commit 2355f290dea87e406538540d680f9da83828c9ca Author: Terra Date: Sun Apr 24 03:37:42 2022 +0200 init diff --git a/Data Format.svg b/Data Format.svg new file mode 100644 index 0000000..3c6a169 --- /dev/null +++ b/Data Format.svg @@ -0,0 +1,180 @@ + + + + + + + + + + TX ID 16 + Length 8 + Data 8 * Length + Hash 64 + + + + + RX ID 16 + + + Message ID 32 + + Max 251 Bytes + + diff --git a/MultiNode-Tester/MultiNode-Tester.ino b/MultiNode-Tester/MultiNode-Tester.ino new file mode 100644 index 0000000..0abaa3a --- /dev/null +++ b/MultiNode-Tester/MultiNode-Tester.ino @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include + + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 +#define MODEM_CONFIG RH_RF95::Bw125Cr45Sf128 +#define MODEM_POWER 13 +#define MODEM_FREQ 868.0 + +const int RF_SS_PIN = 1; +const int RF_IRQ_PIN = 0; +const int BATMON_PIN = A5; +const int BOOSTEN_PIN = 27; + +const float DIVIDER_mA = 183.677; +const float DIVIDER_V = 408.5; + + +byte LoopState = 0; +char inByte; +unsigned long nextTick; +unsigned int msTick = 500; + + +// RFM95 connected to Pin 1 for SS an Pin 0 for interrupt +RH_RF95 rfm95(RF_SS_PIN, RF_IRQ_PIN); + +// 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() +{ + // Pins with internal functions + pinMode(BOOSTEN_PIN, OUTPUT); + digitalWrite(BOOSTEN_PIN, LOW); + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + + // Acivating Motor and Digital Outputs, but at LOW level + pinMode(10, OUTPUT); + digitalWrite(10, LOW); + pinMode(12, OUTPUT); + digitalWrite(12, LOW); + + pinMode(6, OUTPUT); + digitalWrite(6, LOW); + pinMode(7, OUTPUT); + digitalWrite(7, LOW); + + // explcitly activate inputs, to avoid conflicts + pinMode(38, INPUT); + pinMode(2, INPUT); + pinMode(5, INPUT); + pinMode(11, INPUT); + + // Configuring ADC + analogReference(AR_INTERNAL2V23); + analogReadResolution(12); + + + SerialUSB.begin(9600); + //while (!SerialUSB) ; // Wait for serial port to be available + + + if (!rfManager.init()) + SerialUSB.println("init failed"); + + rfm95.setModemConfig(MODEM_CONFIG); + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + // 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: + rfm95.setTxPower(MODEM_POWER, false); + rfm95.setFrequency(MODEM_FREQ); + + // 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: + // rfm95.setCADTimeout(10000); + + + // on-chip teperature sensor + TempZero.init(); + + // activate all five "outputs" for a short period during startup (left to right) + digitalWrite(BOOSTEN_PIN, HIGH); + delay(1000); + digitalWrite(BOOSTEN_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); + + // Print Status Report + PrintSR(); + + // switch of LED after startup + digitalWrite(LED_BUILTIN, LOW); +} + + +// from RadioHead sample +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + + + +void loop() +{ + + /* + SerialUSB.println("Sending to rf95_reliable_datagram_server"); + + // Send a message to manager_server + if (rfManager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + SerialUSB.println("...got an ACK"); + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (rfManager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + SerialUSB.print("got reply from : 0x"); + SerialUSB.print(from, HEX); + SerialUSB.print(": "); + SerialUSB.println((char*)buf); + } + else + { + SerialUSB.println("No reply, is rf95_reliable_datagram_server running?"); + } + } + else + SerialUSB.println("sendtoWait failed"); + */ + + // TICK-ROUTINE + if(LoopState && millis() > nextTick) + { + switch (LoopState){ + case 1: // Read and print Currents + SerialUSB.print(float(analogRead(A1))/DIVIDER_mA, 3); + SerialUSB.print("\t"); + SerialUSB.print(float(analogRead(A2))/DIVIDER_mA, 3); + SerialUSB.print("\t"); + SerialUSB.println(float(analogRead(A3))/DIVIDER_mA, 3); + break; + + case 2: // Read and print Voltages + SerialUSB.print(float(analogRead(A1))/DIVIDER_V, 3); + SerialUSB.print("\t"); + SerialUSB.print(float(analogRead(A2))/DIVIDER_V, 3); + SerialUSB.print("\t"); + SerialUSB.println(float(analogRead(A3))/DIVIDER_V, 3); + break; + + case 3: // Read and print all GPIO and DigINPUTs + SerialUSB.print(digitalRead(8)); + SerialUSB.print("\t"); + SerialUSB.print(digitalRead(9)); + SerialUSB.print("\t \t"); + SerialUSB.print(digitalRead(4)); + SerialUSB.print("\t"); + SerialUSB.print(digitalRead(3)); + SerialUSB.print("\t \t \t"); + SerialUSB.print(digitalRead(38)); + SerialUSB.print("\t"); + SerialUSB.print(digitalRead(2)); + SerialUSB.print("\t \t"); + SerialUSB.print(digitalRead(5)); + SerialUSB.print("\t"); + SerialUSB.println(digitalRead(11)); + break; + + case 4: // Read and Print Battery Voltage + SerialUSB.println(float(analogRead(BATMON_PIN))/918, 3); + break; + + case 5: // Read and Print internal Temperature + SerialUSB.println(TempZero.readInternalTemperature()); + break; + } + + nextTick = millis() + msTick; + } + + // SERIAL CLI + if (SerialUSB.available() > 0) { + + inByte = SerialUSB.read(); + SerialUSB.read(); + + if(LoopState == 1) + digitalWrite(BOOSTEN_PIN, LOW); + //else if(LoopState == 4) + // msTick = 500; + + switch (inByte) { + case 'c': + SerialUSB.println("Currents in mA (switches in lower pos)"); + LoopState = 1; + digitalWrite(BOOSTEN_PIN, HIGH); + break; + case 'v': + SerialUSB.println("Voltages in V (switches in upper pos)"); + LoopState = 2; + break; + case 'd': + SerialUSB.println("GPIO and Digital INPUTS"); + LoopState = 3; + break; + case 'b': + SerialUSB.println("Battery Voltage in V"); + //msTick = 5000; + LoopState = 4; + break; + case 't': + SerialUSB.println("Searching for DS18B20-Sensors, connected to the four 1-Wire pins..."); + + SerialUSB.println("On-Chip Temperature in °C"); + LoopState = 5; + break; + case 's': + SerialUSB.println("Setting Radio Module and MCU in sleep mode for 10 seconds..."); + rfm95.sleep(); + LowPower.deepSleep(10000); + break; + default: + LoopState = 0; + PrintSR(); + break; + } + } + +/* +delay(5000); +rfm95.sleep(); +delay(5000); +LowPower.deepSleep(10000); +*/ + +} + + +void PrintSR(){ + // 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(rfManager.thisAddress()); + SerialUSB.print("\tServer-ID: "); SerialUSB.print(SERVER_ADDRESS); + SerialUSB.print("\tRFM-Version: "); SerialUSB.println(rfm95.getDeviceVersion()); + SerialUSB.print("\tRFM-Message Length: "); SerialUSB.println(rfm95.maxMessageLength()); + 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(rfm95.lastRssi()); + SerialUSB.print("\tFreq-Error: "); SerialUSB.print(rfm95.frequencyError()); + SerialUSB.print("\tLast SNR: "); SerialUSB.println(rfm95.lastSNR()); + SerialUSB.println(); + + SerialUSB.println("********** ATSAMD21G18A MCU *********"); + SerialUSB.print("Internal Temperature (°C): "); + SerialUSB.println(TempZero.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(BOOSTEN_PIN)) + SerialUSB.println("LoopSupply ENABLED (12...15 V)"); + else + SerialUSB.println("LoopSupply DISABLED"); + + SerialUSB.println(); + SerialUSB.println("Send c, v, d, b or t to plot Analog and Digital readings."); + SerialUSB.println("Send s for 10 seconds of sleep mode."); + + // Toggle LED back to previous state + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +} diff --git a/MultiNode/Configurator.py b/MultiNode/Configurator.py new file mode 100644 index 0000000..390780e --- /dev/null +++ b/MultiNode/Configurator.py @@ -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(" + +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(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)); +} diff --git a/MultiNode/MNLib.h b/MultiNode/MNLib.h new file mode 100644 index 0000000..f3e578f --- /dev/null +++ b/MultiNode/MNLib.h @@ -0,0 +1,173 @@ +#pragma once + +#ifndef MNLib_h +#define MNLib_h + +#include +#include +#include + +#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 diff --git a/MultiNode/MultiNode.ino b/MultiNode/MultiNode.ino new file mode 100644 index 0000000..00516ce --- /dev/null +++ b/MultiNode/MultiNode.ino @@ -0,0 +1,156 @@ +#include +#include +#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(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(&configuration), config_size); + break; + + case 'w': + SerialUSB.readBytes(reinterpret_cast(&configuration), config_size); + refreshConfig(); + break; + + case 'R': // Read configuration memory (extended configuration for each sensor) + SerialUSB.write(reinterpret_cast(&configuration_memory), sizeof(configuration_memory)); + break; + + case 'W': + SerialUSB.readBytes(reinterpret_cast(&configuration_memory), sizeof(configuration_memory)); + refreshConfig(); + break; + + case 'k': + char data[sizeof(MNConfiguration)]; + SerialUSB.readBytes(reinterpret_cast(&configuration) + config_size, sizeof(configuration.client_secret_key) + sizeof(configuration.server_secret_key)); + SerialUSB.write(reinterpret_cast(&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); + } +} diff --git a/MultiNodeTest/MultiNodeTest.ino b/MultiNodeTest/MultiNodeTest.ino new file mode 100644 index 0000000..ce984ec --- /dev/null +++ b/MultiNodeTest/MultiNodeTest.ino @@ -0,0 +1,224 @@ +#include +#include +#include "MNLib.h" + +#define IS_CLIENT +//#define IS_SERVER + + +byte LoopState = 0; +char inByte; +unsigned long nextTick; +unsigned int msTick = 500; + + +// RFM95 connected to Pin 1 for SS an Pin 0 for interrupt +RH_RF95 rfm95(RF_SS_PIN, RF_IRQ_PIN); + +// 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() +{ + initialize(true); +} + + +// from RadioHead sample +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + + + +void loop() +{ + + /* + SerialUSB.println("Sending to rf95_reliable_datagram_server"); + + // Send a message to manager_server + if (rfManager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + SerialUSB.println("...got an ACK"); + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (rfManager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + SerialUSB.print("got reply from : 0x"); + SerialUSB.print(from, HEX); + SerialUSB.print(": "); + SerialUSB.println((char*)buf); + } + else + { + SerialUSB.println("No reply, is rf95_reliable_datagram_server running?"); + } + } + else + SerialUSB.println("sendtoWait failed"); + */ + + // TICK-ROUTINE + if (LoopState && millis() > nextTick) + { + switch (LoopState) { + case 1: // Read and print Currents + SerialUSB.print(float(analogRead(A1)) / DIVIDER_mA, 3); + SerialUSB.print("\t"); + SerialUSB.print(float(analogRead(A2)) / DIVIDER_mA, 3); + SerialUSB.print("\t"); + SerialUSB.println(float(analogRead(A3)) / DIVIDER_mA, 3); + break; + + case 2: // Read and print Voltages + SerialUSB.print(float(analogRead(A1)) / DIVIDER_V, 3); + SerialUSB.print("\t"); + SerialUSB.print(float(analogRead(A2)) / DIVIDER_V, 3); + SerialUSB.print("\t"); + SerialUSB.println(float(analogRead(A3)) / DIVIDER_V, 3); + break; + + case 3: // Read and print all GPIO and DigINPUTs + SerialUSB.print(digitalRead(8)); + SerialUSB.print("\t"); + SerialUSB.print(digitalRead(9)); + SerialUSB.print("\t \t"); + SerialUSB.print(digitalRead(4)); + SerialUSB.print("\t"); + SerialUSB.print(digitalRead(3)); + SerialUSB.print("\t \t \t"); + SerialUSB.print(digitalRead(38)); + SerialUSB.print("\t"); + SerialUSB.print(digitalRead(2)); + SerialUSB.print("\t \t"); + SerialUSB.print(digitalRead(5)); + SerialUSB.print("\t"); + SerialUSB.println(digitalRead(11)); + break; + + case 4: // Read and Print Battery Voltage + SerialUSB.println(float(analogRead(BATMON_PIN)) / 918, 3); + break; + + case 5: // Read and Print internal Temperature + SerialUSB.println(TempZero.readInternalTemperature()); + break; + } + + nextTick = millis() + msTick; + } + + // SERIAL CLI + if (SerialUSB.available() > 0) { + + inByte = SerialUSB.read(); + SerialUSB.read(); + + if (LoopState == 1) + digitalWrite(LOOPEN_PIN, LOW); + //else if(LoopState == 4) + // msTick = 500; + + switch (inByte) { + case 'c': + SerialUSB.println("Currents in mA (switches in lower pos)"); + LoopState = 1; + digitalWrite(LOOPEN_PIN, HIGH); + break; + case 'v': + SerialUSB.println("Voltages in V (switches in upper pos)"); + LoopState = 2; + break; + case 'd': + SerialUSB.println("GPIO and Digital INPUTS"); + LoopState = 3; + break; + case 'b': + SerialUSB.println("Battery Voltage in V"); + //msTick = 5000; + LoopState = 4; + break; + case 't': + SerialUSB.println("Searching for DS18B20-Sensors, connected to the four 1-Wire pins..."); + + SerialUSB.println("On-Chip Temperature in °C"); + LoopState = 5; + break; + case 's': + SerialUSB.println("Setting Radio Module and MCU in sleep mode for 10 seconds..."); + rfm95.sleep(); + LowPower.deepSleep(10000); + break; + default: + LoopState = 0; + PrintSR(); + break; + } + } + + /* + delay(5000); + rfm95.sleep(); + delay(5000); + LowPower.deepSleep(10000); + */ + +} + + +void PrintSR() { + // 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(rfManager.thisAddress()); + SerialUSB.print("\tServer-ID: "); SerialUSB.print(configuration.server_address); + //SerialUSB.print("\tRFM-Version: "); SerialUSB.println(rfm95.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(rfm95.lastRssi()); + SerialUSB.print("\tFreq-Error: "); SerialUSB.print(rfm95.frequencyError()); + SerialUSB.print("\tLast SNR: "); SerialUSB.println(rfm95.lastSNR()); + SerialUSB.println(); + + SerialUSB.println("********** ATSAMD21G18A MCU *********"); + SerialUSB.print("Internal Temperature (°C): "); + SerialUSB.println(TempZero.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(); + SerialUSB.println("Send c, v, d, b or t to plot Analog and Digital readings."); + SerialUSB.println("Send s for 10 seconds of sleep mode."); + + // Toggle LED back to previous state + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +} diff --git a/PyRH b/PyRH new file mode 120000 index 0000000..c13d270 --- /dev/null +++ b/PyRH @@ -0,0 +1 @@ +/mnt/ssh/multinode/PyRH \ No newline at end of file diff --git a/RasPi b/RasPi new file mode 120000 index 0000000..5785649 --- /dev/null +++ b/RasPi @@ -0,0 +1 @@ +/mnt/ssh/multinode/RasPi \ No newline at end of file diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..f6518ed --- /dev/null +++ b/notes.txt @@ -0,0 +1,12 @@ +Setup: + Install SAMD boards in Arduino IDE + Install Arduino Low Power + Install TemperatureZero + Install libraries from MultiNode/Libraries + +RasPi: + # Install https://www.airspayce.com/mikem/bcm2835/ on Raspberry Pi + sudo apt-get install pip + pip install numpy pyLoraRFM9x pyblake2 toml + + C++ programs need to be executed with sudo, or set permissions to use bcm2835 as user \ No newline at end of file diff --git a/protocol.txt b/protocol.txt new file mode 100644 index 0000000..160c419 --- /dev/null +++ b/protocol.txt @@ -0,0 +1,6 @@ + + +Each node has a private key shared with the base station (each node its own key or shared between all?) +Each packet has a running number to prevent replay attacks +Public key is sent by the base station to a node during initialization +Hash is determined from RX ID + TX ID + Message ID + Length + Data + Public key + Private key