2022-04-24 03:37:42 +02:00
# include "MNLib.h"
# define FLASH_DEBUG 0
# define EEPROM_EMULATION_SIZE 1024
# include <FlashStorage_SAMD.h>
2022-04-24 21:05:49 +02:00
# include <ArduinoLowPower.h>
2022-04-24 03:37:42 +02:00
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 ] ;
2022-04-24 14:54:40 +02:00
uint32_t last_server_message_id = 0 ;
2022-04-24 22:06:06 +02:00
uint32_t rtc_offset = 0 ;
2022-04-24 03:37:42 +02:00
//void init_mn(bool is_client = true)
2022-04-24 14:54:40 +02:00
void initMN ( )
2022-04-24 03:37:42 +02:00
{
//_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 ( ) ;
2022-04-24 21:05:49 +02:00
initRTC ( ) ;
2022-04-24 22:06:06 +02:00
if ( batteryVoltage ( ) < 3.5 & & ! USBDevice . connected ( ) ) // Shut off below this voltage, down to 3.1 V should be OK if the regulator has a dropout of 400 mV and conducts fully if its output is below 3.3 V
LowPower . deepSleep ( ) ;
2022-04-24 03:37:42 +02:00
}
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 ( " \t Server-ID: " ) ; SerialUSB . print ( configuration . server_address ) ;
//SerialUSB.print("\tRFM-Version: "); SerialUSB.println(radio.getDeviceVersion());
SerialUSB . print ( " Modem-Config: " ) ; SerialUSB . print ( MODEM_CONFIG ) ;
SerialUSB . print ( " \t Frequency: " ) ; SerialUSB . print ( MODEM_FREQ ) ;
SerialUSB . print ( " \t Power: " ) ; SerialUSB . println ( MODEM_POWER ) ;
SerialUSB . print ( " Last RSSI: " ) ; SerialUSB . print ( radio . lastRssi ( ) ) ;
SerialUSB . print ( " \t Freq-Error: " ) ; SerialUSB . print ( radio . frequencyError ( ) ) ;
SerialUSB . print ( " \t Last 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 ( )
{
2022-04-24 22:31:25 +02:00
float frequency = configuration . modem_frequency ;
if ( MODEM_FREQ_LOWER < value & & value < MODEM_FREQ_UPPER )
radio . setFrequency ( configuration . modem_frequency ) ;
else
radio . setFrequency ( 0 ) ;
2022-04-24 03:37:42 +02:00
// 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 ) ;
2022-04-24 14:54:40 +02:00
randomSeed ( configuration . client_address ) ;
2022-04-24 03:37:42 +02:00
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 ] ;
}
2022-04-24 14:54:40 +02:00
hash_generator . reset ( & configuration . client_secret_key , sizeof ( configuration . client_secret_key ) , HASH_LENGTH ) ;
2022-04-24 03:37:42 +02:00
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?
2022-04-24 21:05:49 +02:00
radio . setModeIdle ( ) ;
radio . sleep ( ) ;
2022-04-24 03:37:42 +02:00
return success ;
}
2022-04-24 14:54:40 +02:00
bool receive ( )
{
uint8_t buffer [ RH_RF95_MAX_MESSAGE_LEN ] ;
uint8_t len = sizeof ( buffer ) ;
if ( radio . recv ( buffer , & len ) )
{
2022-04-24 21:05:49 +02:00
radio . setModeIdle ( ) ;
radio . sleep ( ) ;
2022-04-24 14:54:40 +02:00
uint16_t target_address = buffer [ 0 ] + ( ( uint16_t ) buffer [ 1 ] < < 8 ) ;
if ( ( target_address = = configuration . client_address | | target_address = = 0xFFFF ) & & buffer [ 2 ] = = ( configuration . server_address & 0xFF ) & & buffer [ 3 ] = = ( configuration . server_address > > 8 & 0xFF ) )
{
hash_generator . reset ( & configuration . server_secret_key , sizeof ( configuration . server_secret_key ) , HASH_LENGTH ) ;
hash_generator . update ( buffer , len - HASH_LENGTH ) ;
uint8_t hash [ HASH_LENGTH ] ;
hash_generator . finalize ( hash , HASH_LENGTH ) ;
for ( size_t i = 0 ; i < HASH_LENGTH ; i + + )
if ( buffer [ len - HASH_LENGTH + i ] ! = hash [ i ] )
return false ;
uint32_t server_message_id = buffer [ 4 ] | ( ( uint32_t ) buffer [ 5 ] < < 8 ) | ( ( uint32_t ) buffer [ 6 ] < < 16 ) | ( ( uint32_t ) buffer [ 7 ] < < 24 ) ;
if ( server_message_id > last_server_message_id )
{
uint8_t packet_length = buffer [ 8 ] ;
MessageType message_type = ( MessageType ) buffer [ 9 ] ;
switch ( message_type )
{
case MT_Time :
2022-04-24 22:06:06 +02:00
rtc_offset = server_message_id - RTC - > MODE1 . COUNT . reg ;
2022-04-24 22:31:25 +02:00
2022-04-24 14:54:40 +02:00
if ( server_message_id > message_id ) // Ensure that IDs are sequential
message_id = server_message_id ;
2022-04-24 22:06:06 +02:00
2022-04-24 14:54:40 +02:00
return true ;
break ;
}
}
}
}
return false ;
}
2022-04-24 03:37:42 +02:00
void initializeDevices ( )
{
for ( int i = 0 ; i < N_DEVICES ; i + + )
{
devices [ i ] = getDevice ( configuration . devices [ i ] ) ;
devices [ i ] - > initialize ( ) ;
}
}
2022-04-24 15:22:11 +02:00
DeviceBase * getDeviceFromClass ( uint8_t class_type )
{
switch ( class_type )
{
case 0 :
return new Device ;
break ;
case 1 :
return new AnalogInput ;
break ;
default :
return new Device ;
break ;
}
}
2022-04-24 03:37:42 +02:00
DeviceBase * getDevice ( uint8_t pointer )
{
uint8_t class_type = configuration_memory [ pointer ] ;
2022-04-24 15:22:11 +02:00
DeviceBase * device = getDeviceFromClass ( class_type ) ;
2022-04-24 03:37:42 +02:00
if ( class_type = = 0 )
{
device - > class_type = 0 ;
device - > sensor_type = 0xFF ;
device - > pin = 0xFF ;
}
2022-04-24 15:22:11 +02:00
else
memcpy ( reinterpret_cast < uint8_t * > ( device ) + 4 , & configuration_memory [ pointer ] , device - > size ( ) ) ;
2022-04-24 03:37:42 +02:00
2022-04-24 15:22:11 +02:00
return device ;
2022-04-24 03:37:42 +02:00
}
2022-04-24 15:15:09 +02:00
void storeDevice ( uint8_t pointer , DeviceBase * device )
{
memcpy ( & configuration_memory [ pointer ] , reinterpret_cast < uint8_t * > ( device ) + 4 , device - > size ( ) ) ;
}
2022-04-24 14:54:40 +02:00
void loopMN ( )
2022-04-24 03:37:42 +02:00
{
2022-04-24 14:54:40 +02:00
receive ( ) ;
2022-04-24 03:37:42 +02:00
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 ) ) ;
}
2022-04-24 14:54:40 +02:00
void sendDeviceData ( )
{
float temperature = temperature_sensor . readInternalTemperature ( ) ;
float voltage = batteryVoltage ( ) ;
uint8_t data [ 9 ] ;
data [ 0 ] = MT_DeviceStatus ;
memcpy ( & data [ 1 ] , & voltage , 4 ) ;
memcpy ( & data [ 5 ] , & temperature , 4 ) ;
send ( data , 9 ) ;
}
2022-04-24 21:05:49 +02:00
void initRTC ( ) // https://github.com/arduino-libraries/RTCZero/blob/master/src/RTCZero.cpp
{
PM - > APBAMASK . reg | = PM_APBAMASK_RTC ;
# ifndef CRYSTALLESS
SYSCTRL - > XOSC32K . reg = SYSCTRL_XOSC32K_ONDEMAND |
SYSCTRL_XOSC32K_RUNSTDBY |
SYSCTRL_XOSC32K_EN32K |
SYSCTRL_XOSC32K_XTALEN |
SYSCTRL_XOSC32K_STARTUP ( 6 ) |
SYSCTRL_XOSC32K_ENABLE ;
# endif
GCLK - > GENDIV . reg = GCLK_GENDIV_ID ( 2 ) | GCLK_GENDIV_DIV ( 4 ) ;
while ( GCLK - > STATUS . reg & GCLK_STATUS_SYNCBUSY ) ;
# ifdef CRYSTALLESS
GCLK - > GENCTRL . reg = ( GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID ( 2 ) | GCLK_GENCTRL_DIVSEL ) ;
# else
GCLK - > GENCTRL . reg = ( GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID ( 2 ) | GCLK_GENCTRL_DIVSEL ) ;
# endif
while ( GCLK - > STATUS . reg & GCLK_STATUS_SYNCBUSY ) ;
GCLK - > CLKCTRL . reg = ( uint32_t ) ( ( GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | ( RTC_GCLK_ID < < GCLK_CLKCTRL_ID_Pos ) ) ) ;
while ( GCLK - > STATUS . bit . SYNCBUSY ) ;
RTC - > MODE0 . CTRL . reg & = ~ RTC_MODE0_CTRL_ENABLE ; // disable RTC
RTC - > MODE0 . CTRL . reg | = RTC_MODE0_CTRL_SWRST ; // software reset
RTC - > MODE0 . CTRL . reg = RTC_MODE0_CTRL_PRESCALER_DIV1024 | RTC_MODE0_CTRL_MODE_COUNT32 ;
RTC - > MODE0 . CTRL . reg & = ~ RTC_MODE0_CTRL_MATCHCLR ; // disable clear on match
RTC - > MODE0 . CTRL . reg | = RTC_MODE0_CTRL_ENABLE ; // enable RTC
}
uint32_t getRTC ( )
{
2022-04-24 22:06:06 +02:00
return RTC - > MODE1 . COUNT . reg + rtc_offset ;
2022-04-24 21:05:49 +02:00
}