initial version
This commit is contained in:
		
							
								
								
									
										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')
 | 
			
		||||
		Reference in New Issue
	
	Block a user