initial version
This commit is contained in:
commit
8f8c69c37a
1
arduino/.gitignore
vendored
Normal file
1
arduino/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.pio
|
20
arduino/platformio.ini
Normal file
20
arduino/platformio.ini
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:uno]
|
||||||
|
platform = atmelavr
|
||||||
|
board = uno
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
bakercp/PacketSerial@^1.4.0
|
||||||
|
paulstoffregen/OneWire@^2.3.7
|
||||||
|
milesburton/DallasTemperature@^3.11.0
|
||||||
|
ivanseidel/LinkedList@0.0.0-alpha+sha.dac3874d28
|
||||||
|
monitor_encoding = hexlify
|
35
arduino/src/Sensor.cpp
Normal file
35
arduino/src/Sensor.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include <Sensor.hpp>
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline size_t writeType(byte *buffer, T val) {
|
||||||
|
memcpy(buffer, &val, sizeof(T));
|
||||||
|
|
||||||
|
return sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sensor::Sensor(int id, float resolution, float accuracy, DallasTemperature &bus, DeviceAddress address) : id(id), resolution(resolution), accuracy(accuracy), bus(bus) {
|
||||||
|
memcpy(this->address, address, sizeof(DeviceAddress));
|
||||||
|
};
|
||||||
|
|
||||||
|
float Sensor::read() const {
|
||||||
|
bus.requestTemperaturesByAddress(address);
|
||||||
|
return bus.getTempC(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Sensor::makeDataPacket(byte (&buffer)[dataPacketMaxSize]) const {
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
written += writeType(&buffer[written], id);
|
||||||
|
written += writeType(&buffer[written], read());
|
||||||
|
|
||||||
|
return written + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sensor::registerSensor(byte (&buffer)[serializedSize]) const {
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
written += writeType(&buffer[written], id);
|
||||||
|
written += writeType(&buffer[written], resolution);
|
||||||
|
written += writeType(&buffer[written], accuracy);
|
||||||
|
}
|
32
arduino/src/Sensor.hpp
Normal file
32
arduino/src/Sensor.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef SENSOR_HPP
|
||||||
|
#define SENSOR_HPP
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include <DallasTemperature.h>
|
||||||
|
|
||||||
|
class Sensor {
|
||||||
|
private:
|
||||||
|
uint16_t id;
|
||||||
|
uint8_t resolution;
|
||||||
|
float accuracy;
|
||||||
|
DallasTemperature &bus;
|
||||||
|
DeviceAddress address;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr uint16_t serializedSize = sizeof(id) + sizeof(resolution) + sizeof(accuracy);
|
||||||
|
static constexpr uint16_t dataPacketMaxSize = 256;
|
||||||
|
|
||||||
|
|
||||||
|
Sensor(int id, float resolution, float accuracy, DallasTemperature &bus, DeviceAddress address);
|
||||||
|
|
||||||
|
float read() const;
|
||||||
|
|
||||||
|
uint16_t getId() const {return id;};
|
||||||
|
|
||||||
|
uint16_t makeDataPacket(byte (&buffer)[dataPacketMaxSize]) const;
|
||||||
|
|
||||||
|
void registerSensor(byte (&buffer)[serializedSize]) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
59
arduino/src/main.cpp
Normal file
59
arduino/src/main.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <OneWire.h>
|
||||||
|
#include <DallasTemperature.h>
|
||||||
|
#include <PacketSerial.h>
|
||||||
|
#include <LinkedList.h>
|
||||||
|
|
||||||
|
#include <Sensor.hpp>
|
||||||
|
|
||||||
|
constexpr uint8_t oneWirePort = 4;
|
||||||
|
|
||||||
|
enum class PacketType : byte {
|
||||||
|
registration = 0x01,
|
||||||
|
data = 0x02
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
PacketSerial packetSerial;
|
||||||
|
packetSerial.begin(9600);
|
||||||
|
|
||||||
|
OneWire oneWire(oneWirePort);
|
||||||
|
DallasTemperature sensorBus(&oneWire);
|
||||||
|
sensorBus.begin();
|
||||||
|
|
||||||
|
LinkedList<Sensor*> sensors;
|
||||||
|
|
||||||
|
DeviceAddress tempAddress;
|
||||||
|
Sensor *tempSensorAddress;
|
||||||
|
for(int i = 0; i < sensorBus.getDeviceCount(); ++i) {
|
||||||
|
sensorBus.getAddress(tempAddress, i);
|
||||||
|
tempSensorAddress = new Sensor(i, sensorBus.getResolution(tempAddress), 0.5, sensorBus, tempAddress);
|
||||||
|
sensors.add(tempSensorAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte buff[Sensor::serializedSize + 1];
|
||||||
|
buff[0] = (byte)PacketType::registration;
|
||||||
|
for(int i = 0; i < sensors.size(); ++i) {
|
||||||
|
sensors.get(i)->registerSensor((byte(&)[Sensor::serializedSize])buff[1]);
|
||||||
|
packetSerial.send(buff, sizeof(buff));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte dataBuff[Sensor::dataPacketMaxSize + 1];
|
||||||
|
dataBuff[0] = (byte)PacketType::data;
|
||||||
|
uint16_t written;
|
||||||
|
while(true) {
|
||||||
|
for(int i = 0; i < sensors.size(); ++i) {
|
||||||
|
written = sensors.get(i)->makeDataPacket((byte(&)[Sensor::dataPacketMaxSize])dataBuff[1]);
|
||||||
|
packetSerial.send(dataBuff, written);
|
||||||
|
}
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < sensors.size(); ++i) {
|
||||||
|
delete sensors.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
1
raspberryPi/.gitignore
vendored
Normal file
1
raspberryPi/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
db/
|
10
raspberryPi/Dockerfile
Normal file
10
raspberryPi/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM python:3.9.18
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY src .
|
||||||
|
|
||||||
|
CMD ["python", "./main.py"]
|
3
raspberryPi/requirements.txt
Normal file
3
raspberryPi/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pyserial==3.5
|
||||||
|
cobs==1.2.1
|
||||||
|
flask==3.0.0
|
1
raspberryPi/src/.gitignore
vendored
Normal file
1
raspberryPi/src/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
__pycache__/
|
8
raspberryPi/src/main.py
Normal file
8
raspberryPi/src/main.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import threading
|
||||||
|
import reader
|
||||||
|
import website
|
||||||
|
|
||||||
|
readThread = threading.Thread(target=reader.read)
|
||||||
|
websiteThread = threading.Thread(target=website.runWebsite)
|
||||||
|
readThread.start()
|
||||||
|
websiteThread.start()
|
80
raspberryPi/src/reader.py
Normal file
80
raspberryPi/src/reader.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import serial
|
||||||
|
import sqlite3
|
||||||
|
from cobs import cobs
|
||||||
|
import time
|
||||||
|
import struct
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Sensor:
|
||||||
|
id: int
|
||||||
|
accuracy: float
|
||||||
|
resolution: float
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DataPoint:
|
||||||
|
sensorId: int
|
||||||
|
value: float
|
||||||
|
|
||||||
|
def ensureDatabaseExists(con: sqlite3.Connection) -> None:
|
||||||
|
cur = con.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS sensor(
|
||||||
|
sensorId INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
resolution REAL NOT NULL,
|
||||||
|
accuracy REAL NOT NULL
|
||||||
|
);
|
||||||
|
""")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS dataPoint(
|
||||||
|
dataPointId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
sensorFK INTEGER NOT NULL,
|
||||||
|
timestamp INTEGER NOT NULL,
|
||||||
|
value REAL NOT NULL,
|
||||||
|
FOREIGN KEY(sensorFK) REFERENCES sensor(sensorId)
|
||||||
|
);
|
||||||
|
""")
|
||||||
|
|
||||||
|
def ensureSensorExists(con: sqlite3.Connection, sensor: Sensor) -> None:
|
||||||
|
ensureDatabaseExists(con)
|
||||||
|
|
||||||
|
cur = con.cursor()
|
||||||
|
res = cur.execute("SELECT COUNT(sensorId) FROM sensor WHERE sensorId = ?;", (sensor.id,))
|
||||||
|
if(res.fetchone()[0] == 0):
|
||||||
|
cur.execute("INSERT INTO sensor(sensorId, resolution, accuracy) VALUES(?, ?, ?);", (sensor.id, sensor.resolution, sensor.accuracy))
|
||||||
|
|
||||||
|
def addDataPoint(con: sqlite3.Connection, dataPoint: DataPoint) -> bool:
|
||||||
|
ensureDatabaseExists(con)
|
||||||
|
|
||||||
|
cur = con.cursor()
|
||||||
|
res = cur.execute("SELECT COUNT(sensorId) FROM sensor WHERE sensorId = ?;", (dataPoint.sensorId,))
|
||||||
|
if(res.fetchone() == 0):
|
||||||
|
return False
|
||||||
|
cur.execute("INSERT INTO dataPoint(sensorFK, timestamp, value) VALUES(?, ?, ?);", (dataPoint.sensorId, round((datetime.utcnow() - datetime(1970, 1, 1)).total_seconds()), dataPoint.value))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def read() -> None:
|
||||||
|
con = sqlite3.connect("/data/specialIO.db")
|
||||||
|
with serial.Serial("/dev/ttyACM0", 9600) as s:
|
||||||
|
time.sleep(0.1)
|
||||||
|
s.reset_input_buffer()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
packet = cobs.decode(s.read_until(expected=b'\x00')[:-1])
|
||||||
|
if(packet[0] == 0x01):
|
||||||
|
parsed = struct.unpack_from('<HBf', packet, 1)
|
||||||
|
sensor = Sensor(parsed[0], parsed[1], parsed[2])
|
||||||
|
ensureSensorExists(con, sensor)
|
||||||
|
print(f"register: {sensor}")
|
||||||
|
con.commit()
|
||||||
|
elif(packet[0] == 0x02):
|
||||||
|
parsed = struct.unpack_from('<Hf', packet, 1)
|
||||||
|
dataPoint = DataPoint(parsed[0], parsed[1])
|
||||||
|
addDataPoint(con, dataPoint)
|
||||||
|
print(f"data: {dataPoint}")
|
||||||
|
con.commit()
|
||||||
|
else:
|
||||||
|
raise ValueError("Received packet with unknown type", packet)
|
47
raspberryPi/src/static/index.html
Normal file
47
raspberryPi/src/static/index.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" style="margin: 0; height: 100%;">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Sensoren grafiek</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; height: 100%;">
|
||||||
|
<div style="position: relative; height: 100%; width: 100%;">
|
||||||
|
<canvas id="sensorChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(async () => {
|
||||||
|
const ctx = document.getElementById("sensorChart");
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000)
|
||||||
|
|
||||||
|
const jsonData = (await (await fetch(`/api/data/${now-60*10}/${now}`)).json());
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
datasets: Object.entries(jsonData).map(
|
||||||
|
x => ({
|
||||||
|
label: x[0],
|
||||||
|
data: x[1]["data"].map(dataPoint => ({x: dataPoint.time*1000, y: dataPoint.value}))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: "line",
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
24
raspberryPi/src/website.py
Normal file
24
raspberryPi/src/website.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from flask import Flask
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
app = Flask(__name__, static_folder='static')
|
||||||
|
|
||||||
|
@app.route("/api/data/<int:start>/<int:end>")
|
||||||
|
def getData(start: int, end: int) -> dict:
|
||||||
|
dbCon = sqlite3.connect("/data/specialIO.db")
|
||||||
|
cur = dbCon.cursor()
|
||||||
|
|
||||||
|
retVal = {}
|
||||||
|
for sensor in cur.execute("SELECT sensorId, resolution, accuracy FROM sensor;").fetchall():
|
||||||
|
retVal[sensor[0]] = {"resolution": sensor[1], "accuracy": sensor[2], "data":[]}
|
||||||
|
results = cur.execute("SELECT timestamp, value FROM dataPoint WHERE dataPoint.sensorFK = ? AND timestamp >= ? AND timestamp <= ?;", (sensor[0], start, end)).fetchall()
|
||||||
|
retVal[sensor[0]]["data"] = list(map(lambda x: {"time": x[0], "value": x[1]}, results))
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return app.send_static_file("index.html")
|
||||||
|
|
||||||
|
def runWebsite() -> None:
|
||||||
|
app.run(host='0.0.0.0')
|
Loading…
Reference in New Issue
Block a user