This commit is contained in:
2022-04-24 03:37:42 +02:00
commit 2355f290de
14 changed files with 1480 additions and 0 deletions

81
MultiNode/Configurator.py Normal file
View File

@@ -0,0 +1,81 @@
import serial, struct
from glob import glob
port = serial.Serial(port=glob("/dev/serial/by-id/usb-Arduino_LLC_Arduino_Zero_*-if00")[0], baudrate=115200)
port.write(b"c")
config_info = {val.split(b":")[0] : int(val.split(b":")[1]) for val in port.read_until(b"END\r\n").split(b"\r\n")[:-2]}
mn_config_size = config_info[b"MNConfiguration"]
mn_cfgmem_size = config_info[b"CFGMEM"]
n_devices = config_info[b"N_DEVICES"]
mn_device_size = config_info[b"Device"]
mn_analog_input_size = config_info[b"AnalogInput"]
print(config_info)
port.write(b"r")
config = port.read(mn_config_size)
print(config)
port.write(b"R")
cfgmem = port.read(mn_cfgmem_size)
print(cfgmem)
modem_frequency = struct.unpack("<f", config[0:4])[0]
modem_power = config[4]
client_address = int.from_bytes(config[5:7], byteorder="little")
server_address = int.from_bytes(config[7:9], byteorder="little")
pointers = list(config[9 : 9 + n_devices])
modem_frequency = 868.0
modem_power = 0
client_address = 0x1234
server_address = 0x0001
def pack_device():
data = struct.pack("<bbb", 0, 0xFF, 0xFF)
assert len(data) == mn_device_size
return data
def pack_analog_input(sensor_type, pin, min_threshold, max_threshold, multiplier, offset, is_current):
data = struct.pack("<bbbffff?", 1, sensor_type, pin, min_threshold, max_threshold, multiplier, offset, is_current)
assert len(data) == mn_analog_input_size
return data
cfgmem = list(cfgmem)
cfgmem_pointer = 0x0
pointer_counter = 0
def add_to_config(entry):
global cfgmem_pointer, pointer_counter
cfgmem[cfgmem_pointer : cfgmem_pointer + len(entry)] = entry
pointers[pointer_counter] = cfgmem_pointer
cfgmem_pointer += len(entry)
pointer_counter += 1
assert cfgmem_pointer < 256
add_to_config(pack_analog_input(5, 15, -1000, 1000, 1, 0, False))
add_to_config(pack_analog_input(6, 16, 3.8, 20.5, 1, 0, True))
config = list(config)
config[0:4] = struct.pack("<f", modem_frequency)
config[4] = modem_power
config[5:7] = int.to_bytes(client_address, 2, byteorder="little")
config[7:9] = int.to_bytes(server_address, 2, byteorder="little")
config[9 : 9 + n_devices] = pointers
port.write(b"W")
port.write(bytearray(cfgmem))
port.write(b"w")
port.write(bytearray(config))
server_key = 0x2e29b257521dc792
client_key = 0x0#0x7ed64cce5b5d8e85
port.write(b"k")
port.write(struct.pack("<QQ", client_key, server_key))
#port.write(b"s")

Binary file not shown.

Binary file not shown.

Binary file not shown.

333
MultiNode/MNLib.cpp Normal file
View File

@@ -0,0 +1,333 @@
#include "MNLib.h"
#define FLASH_DEBUG 0
#define EEPROM_EMULATION_SIZE 1024
#include <FlashStorage_SAMD.h>
MNConfiguration configuration;
RH_RF95 radio(RF_SS_PIN, RF_IRQ_PIN);
TemperatureZero temperature_sensor = TemperatureZero();
BLAKE2s hash_generator;
uint32_t message_id = 0;
bool _is_client;
uint8_t configuration_memory[CFGMEM];
DeviceBase* devices[N_DEVICES];
//void init_mn(bool is_client = true)
void init_mn()
{
//_is_client = is_client;
EEPROM.setCommitASAP(false); // Don't unnecessarily write to the EEPROM
pinMode(LOOPEN_PIN, OUTPUT);
digitalWrite(LOOPEN_PIN, LOW);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
pinMode(MOTOR10, OUTPUT);
digitalWrite(MOTOR10, LOW);
pinMode(MOTOR12, OUTPUT);
digitalWrite(MOTOR12, LOW);
pinMode(DOUT6, OUTPUT);
digitalWrite(DOUT6, LOW);
pinMode(DOUT7, OUTPUT);
digitalWrite(DOUT7, LOW);
// explicitly activate inputs, to avoid conflicts
pinMode(A1, INPUT);
pinMode(A2, INPUT);
pinMode(A3, INPUT);
pinMode(DIGIN38, INPUT);
pinMode(DIGIN2, INPUT);
pinMode(DIGIN5, INPUT);
pinMode(DIGIN11, INPUT);
pinMode(GPIO8, INPUT);
pinMode(GPIO9, INPUT);
pinMode(GPIO4, INPUT);
pinMode(GPIO3, INPUT);
pinMode(BATMON_PIN, INPUT);
// Activate LED output
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, OFF);
// Configuring ADC
analogReference(AR_INTERNAL2V23);
analogReadResolution(12);
while (!radio.init())
{
SerialUSB.println("Radio init failed");
errorBlink(5);
}
loadMemory();
/*if (configuration.client_address == 0xFFFF) // Check for uninitialized flash
{
configuration.modem_frequency = 868.0;
configuration.modem_power = 10;
configuration.client_address = 0x1FFF;
configuration.server_address = 0x0001;
configuration.client_secret_key = 0;
saveMemory();
loadMemory();
}*/
// Defaults after init are 868.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
radio.setModemConfig(MODEM_CONFIG);
// You can optionally require this module to wait until Channel Activity
// Detection shows no activity on the channel before transmitting by setting
// the CAD timeout to non-zero:
radio.setCADTimeout(1000);
//radio.setHeaderFrom(1);
//radio.setHeaderTo(255);
// on-chip teperature sensor
temperature_sensor.init();
initializeDevices();
}
void test()
{
digitalWrite(LED_BUILTIN, ON);
// activate all five "outputs" for a short period during startup (left to right)
digitalWrite(LOOPEN_PIN, HIGH);
delay(1000);
digitalWrite(LOOPEN_PIN, LOW);
digitalWrite(10, HIGH);
delay(1000);
digitalWrite(10, LOW);
digitalWrite(12, HIGH);
delay(1000);
digitalWrite(12, LOW);
digitalWrite(6, HIGH);
delay(1000);
digitalWrite(6, LOW);
digitalWrite(7, HIGH);
delay(1000);
digitalWrite(7, LOW);
// switch of LED after startup
digitalWrite(LED_BUILTIN, OFF);
// Print Status Report
printStatusReport();
}
void printStatusReport()
{
// Status Report: Dumps a bunch of usefull information to SerialUSB
// Toggle LED
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
SerialUSB.println("**************************************");
SerialUSB.println("********** NetMon MultiNode **********");
SerialUSB.println("**************************************");
SerialUSB.println();
SerialUSB.println(__FILE__);
SerialUSB.print(__DATE__);
SerialUSB.print(" / ");
SerialUSB.println(__TIME__);
SerialUSB.println();
SerialUSB.println("********** RFM95 LoRa Module *********");
SerialUSB.print("Client-ID: "); SerialUSB.print(configuration.client_address);
SerialUSB.print("\tServer-ID: "); SerialUSB.print(configuration.server_address);
//SerialUSB.print("\tRFM-Version: "); SerialUSB.println(radio.getDeviceVersion());
SerialUSB.print("Modem-Config: "); SerialUSB.print(MODEM_CONFIG);
SerialUSB.print("\tFrequency: "); SerialUSB.print(MODEM_FREQ);
SerialUSB.print("\tPower: "); SerialUSB.println(MODEM_POWER);
SerialUSB.print("Last RSSI: "); SerialUSB.print(radio.lastRssi());
SerialUSB.print("\tFreq-Error: "); SerialUSB.print(radio.frequencyError());
SerialUSB.print("\tLast SNR: "); SerialUSB.println(radio.lastSNR());
SerialUSB.println();
SerialUSB.println("********** ATSAMD21G18A MCU *********");
SerialUSB.print("Internal Temperature (°C): ");
SerialUSB.println(temperature_sensor.readInternalTemperature());
SerialUSB.print("Battery Voltage (V): ");
SerialUSB.println(float(analogRead(BATMON_PIN)) / 918);
if (digitalRead(LED_BUILTIN))
SerialUSB.println("Built-In LED ON");
else
SerialUSB.println("Built-In LED OFF");
if (digitalRead(LOOPEN_PIN))
SerialUSB.println("LoopSupply ENABLED (12...15 V)");
else
SerialUSB.println("LoopSupply DISABLED");
SerialUSB.println();
// Toggle LED back to previous state
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
void setLoopPower(bool state)
{
digitalWrite(LOOPEN_PIN, state);
}
void saveMemory() // Use sparingly https://github.com/khoih-prog/FlashStorage_SAMD
{
EEPROM.put(CONFIGURATION_ADDRESS, configuration);
EEPROM.put(CONFIGURATION_MEMORY_ADDRESS, configuration_memory);
EEPROM.commit();
}
void loadMemory()
{
EEPROM.get(CONFIGURATION_ADDRESS, configuration);
EEPROM.get(CONFIGURATION_MEMORY_ADDRESS, configuration_memory);
refreshConfig();
}
void refreshConfig()
{
radio.setFrequency(configuration.modem_frequency);
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 2 to 20 dBm:
radio.setTxPower(configuration.modem_power, false);
initializeDevices();
}
float batteryVoltage()
{
return float(analogRead(BATMON_PIN)) * 2.0 * 2.23 / 4096.0;
}
void errorBlink(int n)
{
for (int i = 0; i < n; i++)
{
delay(ERROR_BLINK_HALF_INTERVAL);
digitalWrite(LED_BUILTIN, HIGH);
delay(ERROR_BLINK_HALF_INTERVAL);
digitalWrite(LED_BUILTIN, LOW);
}
}
bool send(uint8_t data[], uint8_t len)
{
if (2 + 2 + 4 + 1 + len + HASH_LENGTH > RH_RF95_MAX_MESSAGE_LEN)
return false;
// Full data composed of RX ID, TX ID, Message ID, Length, Data, Hash
uint8_t full_data[2 + 2 + 4 + 1 + len + HASH_LENGTH];
uint8_t hash[HASH_LENGTH];
//if (_is_client)
//{
full_data[0] = configuration.server_address & 0xFF;
full_data[1] = (configuration.server_address >> 8) & 0xFF;
full_data[2] = configuration.client_address & 0xFF;
full_data[3] = (configuration.client_address >> 8) & 0xFF;
/*} else {
full_data[0] = configuration.client_address & 0xFF;
full_data[1] = (configuration.client_address >> 8) & 0xFF;
full_data[2] = configuration.server_address & 0xFF;
full_data[3] = (configuration.server_address >> 8) & 0xFF;
}*/
full_data[4] = message_id & 0xFF;
full_data[5] = (message_id >> 8) & 0xFF;
full_data[6] = (message_id >> 16) & 0xFF;
full_data[7] = (message_id >> 24) & 0xFF;
full_data[8] = len;
for (size_t i = 0; i < len; i++)
{
full_data[i + 2 + 2 + 4 + 1] = data[i];
}
hash_generator.reset(&configuration.client_secret_key, sizeof(configuration.client_secret_key), HASH_LENGTH); // Does sizeof() work here?
hash_generator.update(full_data, 2 + 2 + 4 + 1 + len);
hash_generator.finalize(hash, HASH_LENGTH);
for (size_t i = 0; i < HASH_LENGTH; i++)
{
full_data[i + 2 + 2 + 4 + 1 + len] = hash[i];
}
message_id++;
bool success = true;
success = success & radio.send(full_data, 2 + 2 + 4 + 1 + len + HASH_LENGTH);
radio.waitPacketSent(); // What does the returned value here indicate?
return success;
}
void initializeDevices()
{
for (int i = 0; i < N_DEVICES; i++)
{
devices[i] = getDevice(configuration.devices[i]);
devices[i]->initialize();
}
}
DeviceBase* getDevice(uint8_t pointer)
{
uint8_t class_type = configuration_memory[pointer];
if (class_type == 0)
{
DeviceBase* device = new Device;
device->class_type = 0;
device->sensor_type = 0xFF;
device->pin = 0xFF;
return device;
}
if (class_type == 1)
{
DeviceBase* device = new AnalogInput;
//memcpy(device + 4, &configuration_memory[pointer], sizeof(AnalogInput) - 4); // There is some 4 bytes extra at the beginning
memcpy(reinterpret_cast<uint8_t*>(device) + 4, &configuration_memory[pointer], sizeof(AnalogInput) - 4); // There is some 4 bytes extra at the beginning
return device;
}
return new Device;
}
void loopSensors()
{
for (int i = 0; i < N_DEVICES; i++)
{
devices[i]->loop();
}
}
void sendSensorData()
{
uint8_t buffer[N_DEVICES * (sizeof(float) + sizeof(uint8_t) * 2)];
uint16_t send_register = 0;
static_assert(N_DEVICES <= 16, "N_DEVICES too large");
int cnt = 0;
for (int i = 0; i < N_DEVICES; i++)
{
if (devices[i]->doSend())
{
send_register |= 1 << i;
float value = devices[i]->getValue();
memcpy(&buffer[cnt * (sizeof(float) + sizeof(uint8_t) * 2)], &devices[i]->sensor_type, sizeof(uint8_t));
memcpy(&buffer[cnt * (sizeof(float) + sizeof(uint8_t) * 2) + sizeof(uint8_t)], &devices[i]->pin, sizeof(uint8_t));
memcpy(&buffer[cnt * (sizeof(float) + sizeof(uint8_t) * 2) + sizeof(uint8_t) * 2], &value, sizeof(float));
cnt++;
}
}
uint8_t data[N_DEVICES * (sizeof(float) + sizeof(uint8_t) * 2) + sizeof(uint16_t) + sizeof(uint8_t)];
data[0] = MT_SensorStatus;
memcpy(&data[1], &send_register, sizeof(send_register));
memcpy(&data[3], &buffer, cnt * (sizeof(float) + sizeof(uint8_t) * 2));
send(data, sizeof(uint8_t) + sizeof(uint16_t) + cnt * (sizeof(float) + sizeof(uint8_t) * 2));
}

173
MultiNode/MNLib.h Normal file
View File

@@ -0,0 +1,173 @@
#pragma once
#ifndef MNLib_h
#define MNLib_h
#include <RH_RF95.h>
#include <TemperatureZero.h>
#include <BLAKE2s.h>
#define ON HIGH
#define OFF LOW
#define MODEM_CONFIG RH_RF95::Bw125Cr45Sf128
#define MODEM_POWER 13
#define MODEM_FREQ 868.0
#define HASH_LENGTH 8 // 8 bytes
#define RF_SS_PIN 1
#define RF_IRQ_PIN 0
#define BATMON_PIN A5
#define LOOPEN_PIN 27
#define DIVIDER_mA_per_LSB 2.23 / 4096 / 100 * 1000 // Adding 0.5 LSB and dividing by 4095 would be more accurate (approx. 0.01 % error currently)
#define DIVIDER_V_per_LSB 2.23 / 4096 * (180000 + 51100) / 51100
// The following block is kinda unnecessary
#define A1 A1
#define A2 A2
#define A3 A3
#define GPIO8 8
#define GPIO9 9
#define GPIO4 4
#define GPIO3 3
#define DIGIN38 38
#define DIGIN2 2
#define DIGIN5 5
#define DIGIN11 11
#define MOTOR10 10
#define MOTOR12 12
#define DOUT6 6
#define DOUT7 7
#define N_DEVICES 15
#define CFGMEM 256
#define ERROR_BLINK_HALF_INTERVAL 100 // 5 Hz
#define CONFIGURATION_ADDRESS 0
#define CONFIGURATION_MEMORY_ADDRESS 256
struct MNConfiguration
{
float modem_frequency;
int8_t modem_power;
uint16_t client_address;
uint16_t server_address;
uint8_t devices[N_DEVICES];
uint64_t client_secret_key;
uint64_t server_secret_key;
} __attribute__ ((packed));
static_assert(sizeof(MNConfiguration) == 40, "MNConfiguration has the wrong size! Please edit this in the configurator too");
struct DeviceBase
{
uint8_t class_type;
uint8_t sensor_type;
uint8_t pin;
virtual void loop() = 0;
virtual float getValue() = 0;
virtual void setValue(float value) = 0;
virtual void reset() = 0;
virtual bool doSend() = 0;
virtual void initialize() = 0;
virtual void printStatus() = 0;
} __attribute__ ((packed));
static_assert(sizeof(DeviceBase) == 7, "Device size wrong");
struct Device : DeviceBase
{
void loop() {}
float getValue()
{
return NAN;
}
void setValue(float value) {}
void reset() {} // For counters / ratemeters and such
bool doSend()
{
return false;
}
void initialize() {}
void printStatus()
{
SerialUSB.println("This is a dummy device");
}
} __attribute__ ((packed));
static_assert(sizeof(Device) == 7, "Device size wrong");
struct AnalogInput : Device // 20 Bytes, each device gets about 17.06 bytes on average
{
float min_threshold;
float max_threshold;
float multiplier;
float offset;
bool is_current;
void initialize()
{
Device::initialize();
pinMode(pin, INPUT);
}
float getValue()
{
int value = analogRead(pin);
float converted_value;
if (is_current)
converted_value = value * DIVIDER_mA_per_LSB;
else
converted_value = value * DIVIDER_V_per_LSB;
if (converted_value < min_threshold || converted_value > max_threshold)
return NAN;
else
return converted_value * multiplier + offset;
};
bool doSend() {
return true;
}
void printStatus()
{
SerialUSB.print("AnalogInput on pin A");
SerialUSB.println(pin - 14);
}
} __attribute__ ((packed));
static_assert(sizeof(AnalogInput) == 24, "AnalogInput size wrong");
enum MessageType
{
MT_DeviceStatus = 1,
MT_SensorStatus = 2,
};
extern MNConfiguration configuration;
extern uint8_t configuration_memory[CFGMEM];
extern RH_RF95 radio;
extern TemperatureZero temperature_sensor;
extern BLAKE2s hash_generator;
extern uint32_t message_id;
extern DeviceBase* devices[N_DEVICES];
//void init_mn(bool is_client);
void init_mn();
void test();
void printStatusReport();
void setLoopPower(bool state);
void saveMemory();
void loadMemory();
void refreshConfig();
float batteryVoltage();
void errorBlink(int n); // Quickly blink n times
bool send(uint8_t data[], uint8_t len);
void initializeDevices();
DeviceBase* getDevice(uint8_t pointer);
void loopSensors();
void sendSensorData();
#endif

156
MultiNode/MultiNode.ino Normal file
View File

@@ -0,0 +1,156 @@
#include <ArduinoLowPower.h>
#include <SPI.h>
#include "MNLib.h"
//#define IS_CLIENT
//#define IS_SERVER
byte LoopState = 0;
char inByte;
unsigned long nextTick = 0;
unsigned int msTick = 2000;
// Class to manage message delivery and receipt, using the rfm95 declared above
//RHReliableDatagram rfManager(rfm95, CLIENT_ADDRESS);
// Internal on-chip Temperature sensor
TemperatureZero TempZero = TemperatureZero();
void setup()
{
SerialUSB.begin(115200);
//while (!SerialUSB);
/*for (int i = 0; i < 5; i++)
{
delay(1000);
SerialUSB.println("owo");
}*/
init_mn();
/*for (int i = 0; i < 2; i++)
{
delay(1000);
SerialUSB.println("awa");
}
printStatusReport();*/
nextTick = millis();
}
void loop()
{
if (SerialUSB.available())
{
size_t config_size = sizeof(MNConfiguration) - sizeof(configuration.client_secret_key) - sizeof(configuration.server_secret_key); // Don't leak the secret key
char command = SerialUSB.read();
switch (command)
{
case 'C': // Print configuration for in the field debugging
SerialUSB.println("Configuration:");
SerialUSB.print("Frequency: ");
SerialUSB.print(configuration.modem_frequency);
SerialUSB.println(" MHz");
SerialUSB.print("Power: ");
SerialUSB.print(configuration.modem_power);
SerialUSB.println(" dBm");
SerialUSB.print("Client address: ");
SerialUSB.println(configuration.client_address);
SerialUSB.print("Server address: ");
SerialUSB.println(configuration.server_address);
for (int i = 0; i < N_DEVICES; i++)
{
SerialUSB.print("Pointer ");
SerialUSB.print(i);
SerialUSB.print(": ");
SerialUSB.println(configuration.devices[i]);
if (configuration.devices[i] != 255)
{
for(int j = 0; j < sizeof(AnalogInput); j++)
{
SerialUSB.print(reinterpret_cast<uint8_t*>(devices[i])[j]);
SerialUSB.print(" ");
}
SerialUSB.println();
devices[i]->printStatus();
}
}
SerialUSB.println();
SerialUSB.print("Battery voltage: ");
SerialUSB.println(batteryVoltage());
SerialUSB.println();
SerialUSB.println(sizeof(uint16_t) + 1 * (sizeof(float) + sizeof(uint8_t) * 2));
SerialUSB.println(sizeof(uint16_t));
SerialUSB.println(1 * (sizeof(float) + sizeof(uint8_t) * 2));
SerialUSB.println("END");
break;
case 'c':
SerialUSB.print("N_DEVICES: ");
SerialUSB.println(N_DEVICES);
SerialUSB.print("CFGMEM: ");
SerialUSB.println(CFGMEM);
SerialUSB.print("MNConfiguration: ");
SerialUSB.println(sizeof(MNConfiguration) - sizeof(configuration.client_secret_key) - sizeof(configuration.server_secret_key));
SerialUSB.print("Device: ");
SerialUSB.println(sizeof(Device) - 4);
SerialUSB.print("AnalogInput: ");
SerialUSB.println(sizeof(AnalogInput) - 4);
SerialUSB.println("END");
break;
case 'r': // Read configuration (Frequency, which sensors etc.)
SerialUSB.write(reinterpret_cast<char*>(&configuration), config_size);
break;
case 'w':
SerialUSB.readBytes(reinterpret_cast<char*>(&configuration), config_size);
refreshConfig();
break;
case 'R': // Read configuration memory (extended configuration for each sensor)
SerialUSB.write(reinterpret_cast<char*>(&configuration_memory), sizeof(configuration_memory));
break;
case 'W':
SerialUSB.readBytes(reinterpret_cast<char*>(&configuration_memory), sizeof(configuration_memory));
refreshConfig();
break;
case 'k':
char data[sizeof(MNConfiguration)];
SerialUSB.readBytes(reinterpret_cast<char*>(&configuration) + config_size, sizeof(configuration.client_secret_key) + sizeof(configuration.server_secret_key));
SerialUSB.write(reinterpret_cast<char*>(&configuration) + config_size, sizeof(configuration.client_secret_key) + sizeof(configuration.server_secret_key));
refreshConfig();
break;
case 's':
saveMemory();
}
}
loopSensors();
// TICK-ROUTINE
if (millis() > nextTick)
{
sendSensorData();
SerialUSB.println("s");
uint8_t data[5];
data[0] = MT_DeviceStatus;
float bv = batteryVoltage();
memcpy(&data[1], &bv, 4);
send(data, 5);
nextTick = nextTick + msTick;
digitalWrite(LED_BUILTIN, HIGH);
delay(2);
digitalWrite(LED_BUILTIN, LOW);
}
}