charts
This commit is contained in:
parent
9581a3ce2c
commit
345666cb2e
20
Dockerfile
20
Dockerfile
@ -1,9 +1,9 @@
|
|||||||
FROM python:3.12-alpine3.20 AS build_bluepy
|
FROM python:3.12-alpine3.20 AS build_bluepy
|
||||||
|
|
||||||
RUN apk add \
|
RUN apk add \
|
||||||
bluez \
|
|
||||||
make \
|
make \
|
||||||
git \glib-dev \
|
git \
|
||||||
|
glib-dev \
|
||||||
gcc \
|
gcc \
|
||||||
build-base \
|
build-base \
|
||||||
freetype-dev \
|
freetype-dev \
|
||||||
@ -16,20 +16,22 @@ RUN git clone https://github.com/IanHarvey/bluepy.git && \
|
|||||||
python3.12 setup.py install
|
python3.12 setup.py install
|
||||||
|
|
||||||
FROM python:3.12-alpine3.20
|
FROM python:3.12-alpine3.20
|
||||||
|
WORKDIR /src
|
||||||
WORKDIR = /src
|
COPY ./python/src/ .
|
||||||
COPY python/src/ .
|
COPY ./python/requierements.txt .
|
||||||
COPY python/requierements.txt .
|
COPY ./python/docker_entrypoint.sh /
|
||||||
COPY python/docker_entrypoint.sh /
|
|
||||||
RUN mkdir data
|
RUN mkdir data
|
||||||
VOLUME data
|
RUN touch DOCKER
|
||||||
|
VOLUME /src/data
|
||||||
|
|
||||||
RUN apk add sudo bluez
|
RUN apk add --no-cache sudo bluez tzdata
|
||||||
|
ENV TZ=Europe/Berlin
|
||||||
|
|
||||||
# Copy bluepy from the bluepy build stage
|
# Copy bluepy from the bluepy build stage
|
||||||
COPY --from=build_bluepy /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
COPY --from=build_bluepy /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||||
COPY --from=build_bluepy /usr/local/bin /usr/local/bin
|
COPY --from=build_bluepy /usr/local/bin /usr/local/bin
|
||||||
|
|
||||||
RUN pip3.12 install -r requierements.txt && rm requierements.txt
|
RUN pip3.12 install -r requierements.txt && rm requierements.txt
|
||||||
|
# RUN echo '@reboot root python3.12 /src/serve_json.py' >> /etc/crontab
|
||||||
|
|
||||||
ENTRYPOINT sh /docker_entrypoint.sh
|
ENTRYPOINT sh /docker_entrypoint.sh
|
||||||
@ -3,5 +3,5 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
atc_mithermometer_gateway:
|
atc_mithermometer_gateway:
|
||||||
image: dasmoorhuhn/atc-mithermometer-gateway:develop-alpine
|
image: dasmoorhuhn/atc-mithermometer-gateway:develop-alpine
|
||||||
container_name: ATC_MiThermometer_Gateway
|
container_name: ATC_MiThermometer_Gateway_Build
|
||||||
build: .
|
build: .
|
||||||
@ -1,8 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
|
|
||||||
# service dbus start
|
python3.12 api_endpoints.py &
|
||||||
# bluetoothd &
|
sudo python3.12 main.py
|
||||||
|
|
||||||
/bin/sh
|
|
||||||
|
|
||||||
sudo python3 main.py
|
|
||||||
@ -2,3 +2,5 @@ pyyaml
|
|||||||
bs4
|
bs4
|
||||||
lxml
|
lxml
|
||||||
requests
|
requests
|
||||||
|
flask
|
||||||
|
flask_cors
|
||||||
39
python/src/api_endpoints.py
Normal file
39
python/src/api_endpoints.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask
|
||||||
|
from flask import jsonify
|
||||||
|
from flask import send_from_directory
|
||||||
|
from flask_cors import CORS
|
||||||
|
from flask_cors import cross_origin
|
||||||
|
|
||||||
|
|
||||||
|
class API:
|
||||||
|
"""
|
||||||
|
API endpoints
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.app = Flask(import_name='backend', static_folder='/data', static_url_path='')
|
||||||
|
CORS(self.app)
|
||||||
|
self.app.config['CORS_HEADERS'] = 'Content-Type'
|
||||||
|
|
||||||
|
# --------Static Routes-------
|
||||||
|
@self.app.route('/charts')
|
||||||
|
@cross_origin()
|
||||||
|
def serve_index():
|
||||||
|
return send_from_directory('/src', 'chart.html')
|
||||||
|
|
||||||
|
@self.app.route('/json')
|
||||||
|
@cross_origin()
|
||||||
|
def serve_get_list_of_json():
|
||||||
|
workdir, filename = os.path.split(os.path.abspath(__file__))
|
||||||
|
return jsonify(os.listdir(f'{workdir}/data'))
|
||||||
|
|
||||||
|
@self.app.route('/json/<path:path>')
|
||||||
|
@cross_origin()
|
||||||
|
def serve_json(path):
|
||||||
|
workdir, filename = os.path.split(os.path.abspath(__file__))
|
||||||
|
return send_from_directory(f'{workdir}/data', path)
|
||||||
|
|
||||||
|
|
||||||
|
api = API()
|
||||||
|
api.app.run(host='0.0.0.0', port=8000)
|
||||||
128
python/src/chart.html
Normal file
128
python/src/chart.html
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Messdaten Charts</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.chartContainer {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
height: 500px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Messdaten Charts</h1>
|
||||||
|
<div id="charts"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function fetchJSONFiles() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:8000/json');
|
||||||
|
const jsonFiles = await response.json();
|
||||||
|
return jsonFiles;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen der JSON-Dateien:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchData(filename) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:8000/json/${filename}`);
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Fehler beim Abrufen der Datei ${filename}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseChartData(data) {
|
||||||
|
const timestamps = data.map(entry => new Date(entry.timestamp));
|
||||||
|
const temperatures = data.map(entry => entry.temperature);
|
||||||
|
const humidity = data.map(entry => entry.humidity);
|
||||||
|
const name = data.length > 0 ? data[0].name : 'Unbekannt';
|
||||||
|
const room = data.length > 0 ? data[0].room : 'Unbekannt';
|
||||||
|
|
||||||
|
return { timestamps, temperatures, humidity, name, room };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderCharts() {
|
||||||
|
const jsonFiles = await fetchJSONFiles();
|
||||||
|
const chartsContainer = document.getElementById('charts');
|
||||||
|
|
||||||
|
for (const file of jsonFiles) {
|
||||||
|
const data = await fetchData(file);
|
||||||
|
const { timestamps, temperatures, humidity, name, room } = parseChartData(data);
|
||||||
|
|
||||||
|
const chartContainer = document.createElement('div');
|
||||||
|
chartContainer.className = 'chartContainer';
|
||||||
|
chartContainer.innerHTML = `<h2>Gerät: ${name}, Raum: ${room}</h2><canvas></canvas>`;
|
||||||
|
chartsContainer.appendChild(chartContainer);
|
||||||
|
|
||||||
|
const ctx = chartContainer.querySelector('canvas').getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: timestamps,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Temperatur (°C)',
|
||||||
|
data: temperatures,
|
||||||
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Luftfeuchtigkeit (%)',
|
||||||
|
data: humidity,
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
time: {
|
||||||
|
unit: 'minute'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: `Gerät: ${name}, Raum: ${room}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCharts();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,17 +1,19 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import json
|
import json
|
||||||
from data_class import Data
|
from data_class import Data
|
||||||
from devices import Device
|
from devices import Device
|
||||||
|
|
||||||
workdir, filename = os.path.split(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
|
|
||||||
def log_to_json(devices):
|
def log_to_json(devices):
|
||||||
|
workdir, filename = os.path.split(os.path.abspath(__file__))
|
||||||
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
dev, data_obj, from_config = device
|
dev, data_obj, from_config = device
|
||||||
data_obj: Data
|
data_obj: Data
|
||||||
from_config: Device
|
from_config: Device
|
||||||
file_name = f'{workdir}{os.sep}data{os.sep}{str(data_obj.mac).replace(":", "-")}.json'
|
file_name = f'{workdir}/data/{str(data_obj.mac).replace(":", "-")}.json'
|
||||||
|
print(file_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(file_name, 'r') as file: data = json.load(file)
|
with open(file_name, 'r') as file: data = json.load(file)
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
from log_data import log_to_json
|
||||||
from discovery import start_discovery
|
from discovery import start_discovery
|
||||||
|
|
||||||
|
|
||||||
def start_loop(interval=60):
|
def start_loop(interval=60):
|
||||||
while True:
|
while True:
|
||||||
start_discovery()
|
devices = start_discovery()
|
||||||
|
log_to_json(devices)
|
||||||
sleep(interval)
|
sleep(interval)
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
from discovery import start_discovery
|
from discovery import start_discovery
|
||||||
from loop import start_loop
|
|
||||||
from log_data import log_to_json
|
from log_data import log_to_json
|
||||||
|
from loop import start_loop
|
||||||
|
|
||||||
|
|
||||||
devices = start_discovery()
|
# devices = start_discovery()
|
||||||
log_to_json(devices)
|
# log_to_json(devices)
|
||||||
|
|
||||||
|
start_loop()
|
||||||
|
|||||||
@ -1,12 +1,21 @@
|
|||||||
TAG=develop-alpine
|
TAG=develop-alpine
|
||||||
CONTAINER=dasmoorhuhn/atc-mithermometer-gateway:$TAG
|
CONTAINER=dasmoorhuhn/atc-mithermometer-gateway:$TAG
|
||||||
|
|
||||||
sudo killall -9 bluetoothd
|
sudo killall -9 bluetoothd > /dev/null 2>&1
|
||||||
docker stop $CONTAINER > /dev/null 2>&1
|
# docker stop $CONTAINER > /dev/null 2>&1
|
||||||
|
sh stop_docker.sh
|
||||||
|
docker container rm ATC_MiThermometer_Gateway > /dev/null 2>&1
|
||||||
|
echo Start container...
|
||||||
docker run \
|
docker run \
|
||||||
--cap-add=SYS_ADMIN \
|
--cap-add=SYS_ADMIN \
|
||||||
--cap-add=NET_ADMIN \
|
--cap-add=NET_ADMIN \
|
||||||
--net=host \
|
--net=host \
|
||||||
|
--name=ATC_MiThermometer_Gateway \
|
||||||
|
--restart unless-stopped \
|
||||||
|
--tty \
|
||||||
|
-ti \
|
||||||
-v /var/run/dbus/:/var/run/dbus/ \
|
-v /var/run/dbus/:/var/run/dbus/ \
|
||||||
-v ./data:/src/data \
|
-v ./data:/src/data \
|
||||||
$CONTAINER
|
$CONTAINER \
|
||||||
|
|
||||||
|
docker container rm ATC_MiThermometer_Gateway > /dev/null 2>&1
|
||||||
3
stop_docker.sh
Normal file
3
stop_docker.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
echo Stopping container gracefully...
|
||||||
|
docker stop ATC_MiThermometer_Gateway
|
||||||
|
echo Done
|
||||||
Loading…
x
Reference in New Issue
Block a user