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