334 lines
9.5 KiB
C++
334 lines
9.5 KiB
C++
|
#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));
|
||
|
}
|