Skip to content

Instantly share code, notes, and snippets.

@tckb
Created June 13, 2021 10:11
Show Gist options
  • Save tckb/6b8a37da5f4d7a7feb0b8ed827ec93fb to your computer and use it in GitHub Desktop.
Save tckb/6b8a37da5f4d7a7feb0b8ed827ec93fb to your computer and use it in GitHub Desktop.
lorawan_node
#include "LoRaWan_APP.h"
#include "Arduino.h"
#include <CayenneLPP.h>
#include <Display.h>
/*LoRaWan related params */
// Helium OTAA params
// uint8_t devEui[] = {_SECRET_STUFF_};
// uint8_t appEui[] = {_SECRET_STUFF_};
// uint8_t appKey[] = {_SECRET_STUFF_};
// TTN OTAA params`
uint8_t devEui[] = {_SECRET_STUFF_};
uint8_t appEui[] = {_SECRET_STUFF_};
uint8_t appKey[] = {_SECRET_STUFF_};
// ABP PARAM
// NOT USED BUT STILL TO MENTION
uint8_t nwkSKey[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t appSKey[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint32_t devAddr = (uint32_t)0x00000000;
// ABP PARAM
uint16_t userChannelsMask[6] = {0x00FF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
// Default LORaWan Config
LoRaMacRegion_t loraWanRegion = ACTIVE_REGION; // Set to REGION_EU868
DeviceClass_t loraWanClass = LORAWAN_CLASS; // Set to CLASS_A
uint32_t appTxDutyCycle = 15000; //the application data transmission duty cycle in ms
bool overTheAirActivation = LORAWAN_NETMODE; // OTAA mode from the menu
bool loraWanAdr = LORAWAN_ADR; // Adaptive Bit Rate - normally off
bool keepNet = LORAWAN_NET_RESERVE; // normally OFF
bool isTxConfirmed = LORAWAN_UPLINKMODE; // if the node is sending confirmed or unconfirmed messages; set to 'Unconfirmed'
uint8_t appPort = 2;
uint8_t confirmedNbTrials = 4;
// other params
bool sleepMode = false;
uint32_t lastScreenPrint = 0;
uint32_t joinStart = 0;
// important sensory information
float data_bat_per = 100;
uint16 data_bat_vol = 0;
uint8 data_bat_level = 0;
CayenneLPP lpp(LORAWAN_APP_DATA_MAX_SIZE);
/* Main code starts here */
void enable_ext_vol_supply(void)
{
pinMode(Vext, OUTPUT);
digitalWrite(Vext, LOW);
delay(100);
}
void disable_ext_vol_supply(void)
{
pinMode(Vext, OUTPUT);
digitalWrite(Vext, HIGH);
}
void cal_battery_level()
{
uint32 time = millis();
bool read_sensor = false;
// don't calculate the battery as often as it
if (lastScreenPrint == 0)
{
read_sensor = true;
lastScreenPrint = time;
}
else
{
if ((time - lastScreenPrint) >= 15000)
{
read_sensor = true;
lastScreenPrint = time;
}
else
{
return;
}
}
if (read_sensor)
{
data_bat_level = BoardGetBatteryLevel();
data_bat_vol = getBatteryVoltage();
data_bat_per = round(((float_t)data_bat_level - BAT_LEVEL_EMPTY) * 100 / (BAT_LEVEL_FULL - BAT_LEVEL_EMPTY));
set_battery_percent(data_bat_per);
set_battery_level(data_bat_level);
}
#ifdef DEBUG
Serial.println();
Serial.print("battery_level:");
Serial.println(battery_level);
Serial.print("battery_level_per: ");
Serial.print(battery_level_per);
#endif
}
bool is_lora_joined()
{
bool joined = false;
MibRequestConfirm_t mibReq;
LoRaMacStatus_t status;
mibReq.Type = MIB_NETWORK_JOINED;
status = LoRaMacMibGetRequestConfirm(&mibReq);
if (status == LORAMAC_STATUS_OK)
{
if (mibReq.Param.IsNetworkJoined == true)
{
joined = true;
}
}
return joined;
}
void goto_sleep(bool wakeupDisplay = true)
{
sleepMode = true;
if (wakeupDisplay)
{
display_wakeup();
}
set_main_status("going to sleep in 4s");
delay(4000);
display_sleep();
deviceState = DEVICE_STATE_CYCLE;
}
void wakeup_from_sleep(bool wakeupDisplay = true)
{
sleepMode = false;
if (wakeupDisplay)
{
enable_ext_vol_supply();
display_init();
display_wakeup();
}
display_clear();
display_status_bar();
set_main_status("waking up in 4s");
delay(4000);
deviceState = DEVICE_STATE_SEND;
}
/*
when user press this button less than 700ms , we either wakeup from sleep
or we go back to sleep
*/
void user_key_handler(void)
{
delay(10);
if (digitalRead(P3_3) == 0)
{
uint16_t keyDownTime = 0;
while (digitalRead(P3_3) == 0)
{
delay(1);
keyDownTime++;
if (keyDownTime >= 700)
break;
}
if (keyDownTime < 700)
{
if (sleepMode)
{
wakeup_from_sleep();
}
else
{
goto_sleep();
}
}
}
}
static void prepare_lora_tx_frame(uint8_t port)
{
lpp.addDigitalOutput(1, data_bat_vol);
lpp.addDigitalOutput(2, data_bat_per);
appDataSize = lpp.getSize();
lpp.copy(appData);
lpp.reset();
}
///////////
// Lora Display
///////////
void display_lora_init()
{
network_status(false);
uplink_status(false);
downlink_status(false);
cal_battery_level();
display_status_bar();
set_main_status("Initializing...");
}
void display_lora_joining()
{
network_status(false);
uplink_status(true);
downlink_status(false);
ack_status(false);
cal_battery_level();
display_status_bar();
set_main_status("Join Requested...");
}
void display_lora_sending()
{
network_status(true);
uplink_status(true);
downlink_status(false);
ack_status(false);
enable_ext_vol_supply();
cal_battery_level();
display_status_bar();
set_main_status("preparing to send...");
if (LoRaWAN.LastDownlinkRssi() != 0)
{
String link_status = "Link: " + String(LoRaWAN.LastDownlinkRssi()) + "rssi / " + LoRaWAN.LastDownlinkSNR() + "db";
set_footer_status(link_status);
}
delay(1000);
}
void display_lora_acknowledge()
{
if (!is_lora_joined())
{
network_status(false);
uplink_status(true);
downlink_status(false);
ack_status(false);
cal_battery_level();
display_status_bar();
set_main_status("Waiting to join...");
}
else
{
if (!sleepMode)
{
// if the display is sleeping
display_wakeup();
if (LoRaWAN.receivedUplinkAck())
{
LoRaWAN.toggleUplinkAck();
network_status(true);
uplink_status(true);
downlink_status(true);
ack_status(true);
display_status_bar();
}
else
{
uplink_status(true);
network_status(true);
if (get_ack_status())
{
// keep showing ack if this is already on
ack_status(true);
}
downlink_status(true);
display_status_bar();
// nothing to do here
set_main_status("Data sent...");
}
if (LoRaWAN.LastDownlinkRssi() != 0)
{
String link_status = "Link: " + String(LoRaWAN.LastDownlinkRssi()) + "rssi / " + LoRaWAN.LastDownlinkSNR() + "db";
set_footer_status(link_status);
}
}
else
{
display_sleep();
disable_ext_vol_supply();
}
}
}
///////////
// setup and loop
///////////
void setup()
{
boardInitMcu();
Serial.begin(115200);
#if (AT_SUPPORT)
enableAt();
#endif
enable_ext_vol_supply();
display_init();
show_welcome();
deviceState = DEVICE_STATE_INIT;
LoRaWAN.ifskipjoin();
cal_battery_level();
//Setup user button - this must be after LoRaWAN.ifskipjoin(), because the button is used there to cancel stored settings load and initiate a new join
pinMode(P3_3, INPUT);
attachInterrupt(P3_3, user_key_handler, FALLING);
}
void loop()
{
switch (deviceState)
{
case DEVICE_STATE_INIT:
display_lora_init();
#if (AT_SUPPORT)
getDevParam();
#endif
printDevParam();
LoRaWAN.init(loraWanClass, loraWanRegion);
LoRaWAN.setDataRateForNoADR(0);
deviceState = DEVICE_STATE_JOIN;
break;
case DEVICE_STATE_JOIN:
display_lora_joining();
LoRaWAN.join();
break;
case DEVICE_STATE_SEND:
// User pressed the button while we were waiting for the next send timer
if (sleepMode)
{
// Send to Cycle so it could setup a sleep timer if not done yet
deviceState = DEVICE_STATE_CYCLE;
}
else
{
prepare_lora_tx_frame(appPort);
display_lora_sending();
LoRaWAN.send();
// Schedule next send
deviceState = DEVICE_STATE_CYCLE;
}
break;
case DEVICE_STATE_CYCLE:
txDutyCycleTime = appTxDutyCycle + randr(0, APP_TX_DUTYCYCLE_RND);
LoRaWAN.cycle(txDutyCycleTime);
deviceState = DEVICE_STATE_SLEEP;
break;
case DEVICE_STATE_SLEEP:
display_lora_acknowledge();
LoRaWAN.sleep();
break;
default:
deviceState = DEVICE_STATE_INIT;
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment