commit 2355f290dea87e406538540d680f9da83828c9ca
Author: Terra <terra@clerie.de>
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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="210mm"
+   height="297mm"
+   viewBox="0 0 210 297"
+   version="1.1"
+   id="svg5"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="Data Format.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="true"
+     inkscape:zoom="1.3336319"
+     inkscape:cx="412.78255"
+     inkscape:cy="344.17292"
+     inkscape:window-width="1600"
+     inkscape:window-height="863"
+     inkscape:window-x="0"
+     inkscape:window-y="37"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:snap-bbox="true"
+     inkscape:snap-bbox-midpoints="true"
+     inkscape:snap-text-baseline="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid844"
+       spacingx="1.0583333"
+       spacingy="1.0583333"
+       empspacing="4" />
+  </sodipodi:namedview>
+  <defs
+     id="defs2" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
+       x="54.60339"
+       y="98.907654"
+       id="text1937"><tspan
+         sodipodi:role="line"
+         id="tspan1935"
+         style="font-size:4.23333px;stroke-width:0.264583"
+         x="54.60339"
+         y="98.907654">TX ID 16</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
+       x="117.6166"
+       y="98.534546"
+       id="text8299"><tspan
+         sodipodi:role="line"
+         id="tspan8297"
+         style="stroke-width:0.264583"
+         x="117.6166"
+         y="98.534546">Length 8</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
+       x="144.06046"
+       y="98.534538"
+       id="text11065"><tspan
+         sodipodi:role="line"
+         id="tspan11063"
+         style="stroke-width:0.264583"
+         x="144.06046"
+         y="98.534538">Data 8 * Length</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
+       x="186.03824"
+       y="98.944862"
+       id="text12091"><tspan
+         sodipodi:role="line"
+         id="tspan12089"
+         style="stroke-width:0.264583"
+         x="186.03824"
+         y="98.944862">Hash 64</tspan></text>
+    <rect
+       style="opacity:0.991674;fill:none;stroke:#000000;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+       id="rect12767"
+       width="25.4"
+       height="8.4666662"
+       x="50.799999"
+       y="93.133331" />
+    <rect
+       style="opacity:0.991674;fill:none;stroke:#000000;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+       id="rect12835"
+       width="25.4"
+       height="8.4666662"
+       x="114.3"
+       y="93.133331" />
+    <rect
+       style="opacity:0.991674;fill:none;stroke:#000000;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+       id="rect12837"
+       width="42.333332"
+       height="8.4666662"
+       x="139.7"
+       y="93.133331" />
+    <rect
+       style="opacity:0.991674;fill:none;stroke:#000000;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+       id="rect12839"
+       width="25.4"
+       height="8.4666662"
+       x="182.03333"
+       y="93.133331" />
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
+       x="28.811686"
+       y="98.907654"
+       id="text1937-3"><tspan
+         sodipodi:role="line"
+         id="tspan1935-6"
+         style="font-size:4.23333px;stroke-width:0.264583"
+         x="28.811686"
+         y="98.907654">RX ID 16</tspan></text>
+    <rect
+       style="opacity:0.991674;fill:none;stroke:#000000;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+       id="rect12767-7"
+       width="25.4"
+       height="8.4666662"
+       x="25.4"
+       y="93.133331" />
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:none;stroke:#000000;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+       x="131.23332"
+       y="80.433334"
+       id="text18838"><tspan
+         sodipodi:role="line"
+         id="tspan18836"
+         style="stroke-width:1.05833"></tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
+       x="95.25412"
+       y="98.497345"
+       id="text12091-5"><tspan
+         sodipodi:role="line"
+         id="tspan12089-3"
+         style="text-align:center;text-anchor:middle;stroke-width:0.264583"
+         x="95.25412"
+         y="98.497345">Message ID 32</tspan></text>
+    <rect
+       style="opacity:0.991674;fill:none;stroke:#000000;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+       id="rect20031"
+       width="38.099998"
+       height="8.4666662"
+       x="76.199997"
+       y="93.133331" />
+    <text
+       xml:space="preserve"
+       style="font-size:4.23333px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';opacity:0.991674;fill:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
+       x="71.53672"
+       y="65.040993"
+       id="text1937-5"><tspan
+         sodipodi:role="line"
+         id="tspan1935-62"
+         style="font-size:4.23333px;stroke-width:0.264583"
+         x="71.53672"
+         y="65.040993">Max 251 Bytes</tspan></text>
+  </g>
+</svg>
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 <ArduinoLowPower.h>
+#include <RHReliableDatagram.h>
+#include <RH_RF95.h>
+#include <SPI.h>
+#include <TemperatureZero.h>
+
+
+#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("<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")
\ No newline at end of file
diff --git a/MultiNode/Libraries/Crypto.zip b/MultiNode/Libraries/Crypto.zip
new file mode 100644
index 0000000..9cfa1d8
Binary files /dev/null and b/MultiNode/Libraries/Crypto.zip differ
diff --git a/MultiNode/Libraries/FlashStorage_SAMD.zip b/MultiNode/Libraries/FlashStorage_SAMD.zip
new file mode 100644
index 0000000..bdeecd3
Binary files /dev/null and b/MultiNode/Libraries/FlashStorage_SAMD.zip differ
diff --git a/MultiNode/Libraries/RadioHead.zip b/MultiNode/Libraries/RadioHead.zip
new file mode 100644
index 0000000..c228882
Binary files /dev/null and b/MultiNode/Libraries/RadioHead.zip differ
diff --git a/MultiNode/MNLib.cpp b/MultiNode/MNLib.cpp
new file mode 100644
index 0000000..a64291c
--- /dev/null
+++ b/MultiNode/MNLib.cpp
@@ -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));
+}
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 <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
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 <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);
+  }
+}
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 <ArduinoLowPower.h>
+#include <SPI.h>
+#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