Compare commits
3
Commits
7712870d1f
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2d268c5a0 | ||
|
|
ceb2431f8e | ||
|
|
29a65a6f9d |
@@ -48,12 +48,34 @@ python seed.py
|
|||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python app.py
|
python app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The server starts on `http://localhost:5000`.
|
The server starts on `http://localhost:5000`.
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies (includes gunicorn)
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Run with gunicorn
|
||||||
|
gunicorn -c gunicorn.conf.py "app:create_app()"
|
||||||
|
```
|
||||||
|
|
||||||
|
Gunicorn starts on `http://0.0.0.0:8000` with `cpu * 2 + 1` workers.
|
||||||
|
|
||||||
|
#### systemd service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp yggdrasil.service /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now yggdrasil
|
||||||
|
```
|
||||||
|
|
||||||
| Route | Description |
|
| Route | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `/sensors` | Dashboard with interactive chart and data table |
|
| `/sensors` | Dashboard with interactive chart and data table |
|
||||||
|
|||||||
+2
-2
@@ -48,7 +48,7 @@ def create_app():
|
|||||||
readouts = query.order_by(SensorReadout.timestamp.desc()).limit(300).all()
|
readouts = query.order_by(SensorReadout.timestamp.desc()).limit(300).all()
|
||||||
return jsonify(
|
return jsonify(
|
||||||
[
|
[
|
||||||
{"value": r.value, "timestamp": r.timestamp.isoformat()}
|
{"value": r.value, "timestamp": r.timestamp.isoformat() + "Z"}
|
||||||
for r in readouts
|
for r in readouts
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -124,7 +124,7 @@ def create_app():
|
|||||||
"sensor_id": readout.sensor_id,
|
"sensor_id": readout.sensor_id,
|
||||||
"type": readout.type,
|
"type": readout.type,
|
||||||
"value": readout.value,
|
"value": readout.value,
|
||||||
"timestamp": readout.timestamp.isoformat(),
|
"timestamp": readout.timestamp.isoformat() + "Z",
|
||||||
}
|
}
|
||||||
), 201
|
), 201
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
bind = "0.0.0.0:8000"
|
||||||
|
workers = min(os.cpu_count() * 2 + 1, 9)
|
||||||
|
timeout = 30
|
||||||
|
keepalive = 5
|
||||||
|
errorlog = "-"
|
||||||
|
accesslog = "-"
|
||||||
|
loglevel = "info"
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
flask>=3.0
|
flask>=3.0
|
||||||
flask-sqlalchemy>=3.1
|
flask-sqlalchemy>=3.1
|
||||||
alembic>=1.13
|
alembic>=1.13
|
||||||
|
gunicorn>=22.0
|
||||||
@@ -61,11 +61,12 @@
|
|||||||
|
|
||||||
function formatTimestamp(iso) {
|
function formatTimestamp(iso) {
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
const pad = (n) => String(n).padStart(2, '0');
|
return d.toLocaleString('pl-PL', { timeZone: 'Europe/Warsaw' });
|
||||||
const months = ['Jan','Feb','Mar','Apr','May','Jun',
|
}
|
||||||
'Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
||||||
return `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear()}, ` +
|
function formatChartLabel(iso) {
|
||||||
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
const d = new Date(iso + 'Z');
|
||||||
|
return d.toLocaleString('pl-PL', { timeZone: 'Europe/Warsaw', hour: '2-digit', minute: '2-digit', day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchJSON(url) {
|
async function fetchJSON(url) {
|
||||||
@@ -114,7 +115,7 @@
|
|||||||
|
|
||||||
// fetch hourly data for chart
|
// fetch hourly data for chart
|
||||||
const hourly = await fetchJSON(`/api/sensors/${sensorId}/readouts/hourly?type=${type}`);
|
const hourly = await fetchJSON(`/api/sensors/${sensorId}/readouts/hourly?type=${type}`);
|
||||||
const labels = hourly.map(r => r.timestamp.replace('T', ' '));
|
const labels = hourly.map(r => formatChartLabel(r.timestamp));
|
||||||
const values = hourly.map(r => r.value);
|
const values = hourly.map(r => r.value);
|
||||||
|
|
||||||
if (chart) chart.destroy();
|
if (chart) chart.destroy();
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Yggdrasil Sensor API
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=mateusz
|
||||||
|
Group=mateusz
|
||||||
|
WorkingDirectory=/home/mateusz/prog/yggdrasil/server
|
||||||
|
ExecStart=/home/mateusz/prog/yggdrasil/server/.venv/bin/gunicorn -c gunicorn.conf.py "app:create_app()"
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -18,9 +18,12 @@ static const char *TAG = "thermo_sensor";
|
|||||||
bmp280_t dev_bmp_280;
|
bmp280_t dev_bmp_280;
|
||||||
|
|
||||||
void main_loop(void *pvParameters) {
|
void main_loop(void *pvParameters) {
|
||||||
|
// Wait 1s, to avoid corrupted first sensor readout
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
while (1) {
|
while (1) {
|
||||||
ygg_bmp280_read_sensor(&dev_bmp_280);
|
ygg_bmp280_read_sensor(&dev_bmp_280);
|
||||||
ygg_send_sensor_readouts(SENSOR_NAME,
|
ygg_send_sensor_readouts(BASE_URL,
|
||||||
|
SENSOR_NAME,
|
||||||
CURR_READOUT);
|
CURR_READOUT);
|
||||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,13 +106,14 @@ char* ygg_send_post(const char *url, char *post_data) {
|
|||||||
int ygg_get_sensor_id(const char *sensor_name,
|
int ygg_get_sensor_id(const char *sensor_name,
|
||||||
const char *url);
|
const char *url);
|
||||||
|
|
||||||
void ygg_send_sensor_readouts(const char *sensor_name,
|
void ygg_send_sensor_readouts(const char *base_url,
|
||||||
|
const char *sensor_name,
|
||||||
ygg_sensor_readout readout) {
|
ygg_sensor_readout readout) {
|
||||||
char post_data[64];
|
char post_data[64];
|
||||||
int sensor_id = ygg_get_sensor_id(sensor_name, "http://192.168.0.236:5000");
|
int sensor_id = ygg_get_sensor_id(sensor_name, base_url);
|
||||||
char url[128];
|
char url[128];
|
||||||
snprintf(url, sizeof(url),
|
snprintf(url, sizeof(url),
|
||||||
"http://192.168.0.236:5000/api/sensors/%d/readouts", sensor_id);
|
"%s/api/sensors/%d/readouts", base_url, sensor_id);
|
||||||
|
|
||||||
snprintf(post_data, sizeof(post_data),
|
snprintf(post_data, sizeof(post_data),
|
||||||
"{\"type\":\"temperature\", \"value\":%.2f}", readout.temperature);
|
"{\"type\":\"temperature\", \"value\":%.2f}", readout.temperature);
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#include "ygg_state.h"
|
#include "ygg_state.h"
|
||||||
|
|
||||||
void ygg_send_sensor_readouts(const char *sensor_name,
|
void ygg_send_sensor_readouts(const char *base_url,
|
||||||
|
const char *sensor_name,
|
||||||
ygg_sensor_readout readout);
|
ygg_sensor_readout readout);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ typedef struct {
|
|||||||
} ygg_sensor_readout;
|
} ygg_sensor_readout;
|
||||||
|
|
||||||
extern ygg_sensor_readout CURR_READOUT;
|
extern ygg_sensor_readout CURR_READOUT;
|
||||||
static const char *SENSOR_NAME = "BMP_280_SENSOR_001";
|
static const char *SENSOR_NAME = "BMP_280";
|
||||||
|
static const char *BASE_URL = "http://192.168.0.236:5000";
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user