Skip to content

Instantly share code, notes, and snippets.

@proffalken
Created November 5, 2022 06:56
Show Gist options
  • Save proffalken/d7ef6f569cbbfaa2374e7aa6f20ae6a9 to your computer and use it in GitHub Desktop.
Save proffalken/d7ef6f569cbbfaa2374e7aa6f20ae6a9 to your computer and use it in GitHub Desktop.
LoRaWAN from JSON API
void LoRaWanClass::join()
{
if( overTheAirActivation == true )
{
Serial.println("joining...");
MlmeReq_t mlmeReq;
mlmeReq.Type = MLME_JOIN;
mlmeReq.Req.Join.DevEui = DevEui;
mlmeReq.Req.Join.AppEui = AppEui;
mlmeReq.Req.Join.AppKey = AppKey;
mlmeReq.Req.Join.NbTrials = 1;
/******* FAILING FUNCTION CALL (Line 484) *********/
if( LoRaMacMlmeRequest( &mlmeReq ) == LORAMAC_STATUS_OK )
/******* END FAILING FUNCTION CALL ********/
{
deviceState = DEVICE_STATE_SLEEP;
}
else
{
deviceState = DEVICE_STATE_CYCLE;
}
}
switch ( mlmeRequest->Type ) {
case MLME_JOIN: {
if ( ( mlmeRequest->Req.Join.DevEui == NULL ) ||
( mlmeRequest->Req.Join.AppEui == NULL ) ||
( mlmeRequest->Req.Join.AppKey == NULL ) )
{
return LORAMAC_STATUS_PARAMETER_INVALID;
}
// Verify the parameter NbTrials for the join procedure
verify.NbJoinTrials = mlmeRequest->Req.Join.NbTrials;
if ( RegionVerify( LoRaMacRegion, &verify, PHY_NB_JOIN_TRIALS ) == false ) {
// Value not supported, get default
getPhy.Attribute = PHY_DEF_NB_JOIN_TRIALS;
phyParam = RegionGetPhyParam( LoRaMacRegion, &getPhy );
mlmeRequest->Req.Join.NbTrials = ( uint8_t ) phyParam.Value;
}
LoRaMacFlags.Bits.MlmeReq = 1;
queueElement.Request = mlmeRequest->Type;
LoRaMacDevEui = mlmeRequest->Req.Join.DevEui;
LoRaMacAppEui = mlmeRequest->Req.Join.AppEui;
LoRaMacAppKey = mlmeRequest->Req.Join.AppKey;
queueElement.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
queueElement.RestrictCommonReadyToHandle = false;
/********** ERROR RAISED BY NEXT LINE (Line 3181) ******/
LoRaMacConfirmQueueAdd( &queueElement );
/*********** ERROR ENDS **************************/
MaxJoinRequestTrials = mlmeRequest->Req.Join.NbTrials;
// Reset variable JoinRequestTrials
JoinRequestTrials = 0;
// Setup header information
macHdr.Value = 0;
macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ;
ResetMacParameters( );
altDr.NbTrials = JoinRequestTrials + 1;
LoRaMacParams.ChannelsDatarate = RegionAlternateDr( LoRaMacRegion, &altDr );
status = Send( &macHdr, 0, NULL, 0 );
break;
}
bool LoRaMacConfirmQueueAdd( MlmeConfirmQueue_t* mlmeConfirm )
{
if( MlmeConfirmQueueCnt >= LORA_MAC_MLME_CONFIRM_QUEUE_LEN )
{
// Protect the buffer against overwrites
return false;
}
// Add the element to the ring buffer
BufferEnd->Request = mlmeConfirm->Request; // <------- Line 134
BufferEnd->Status = mlmeConfirm->Status;
BufferEnd->RestrictCommonReadyToHandle = mlmeConfirm->RestrictCommonReadyToHandle;
BufferEnd->ReadyToHandle = false;
// Increase counter
MlmeConfirmQueueCnt++;
// Update end pointer
BufferEnd = IncreaseBufferPointer( BufferEnd );
return true;
}
#define LORA_PREAMBLE_LENGTH 8
#include <ESP32_LoRaWAN.h>
#include <Arduino.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <Preferences.h>
#include <WiFi.h>
// Setup the ability to read from the environment
#define ST(A) #A
#define STR(A) ST(A)
// setup the basic counter
int counter=0;
/*LoraWan channelsmask, default channels 0-7*/
uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 };
/*LoraWan Class, Class A and Class C are supported*/
DeviceClass_t loraWanClass = CLASS_A;
/*OTAA or ABP*/
bool overTheAirActivation = true;
/*the application data transmission duty cycle. value in [ms].*/
uint32_t appTxDutyCycle = 15000;
/*ADR enable*/
bool loraWanAdr = true;
/* Indicates if the node is sending confirmed or unconfirmed messages */
bool isTxConfirmed = true;
/* Application port */
uint8_t appPort = 2;
/*!
* Number of trials to transmit the frame, if the LoRaMAC layer did not
* receive an acknowledgment. The MAC performs a datarate adaptation,
* according to the LoRaWAN Specification V1.0.2, chapter 18.4, according
* to the following table:
*
* Transmission nb | Data Rate
* ----------------|-----------
* 1 (first) | DR
* 2 | DR
* 3 | max(DR-1,0)
* 4 | max(DR-1,0)
* 5 | max(DR-2,0)
* 6 | max(DR-2,0)
* 7 | max(DR-3,0)
* 8 | max(DR-3,0)
*
* Note, that if NbTrials is set to 1 or 2, the MAC will not decrease
* the datarate, in case the LoRaMAC layer did not receive an acknowledgment
*/
uint8_t confirmedNbTrials = 8;
/* ABP para - NOT NEEDED BECAUSE OTAA, BUT ALSO NEEDED BECAUSE HELTEC CODE IS CRAP*/
uint8_t NwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda,0x85 };
uint8_t AppSKey[] = { 0xd7, 0x2c, 0x78, 0x75, 0x8c, 0xdc, 0xca, 0xbf, 0x55, 0xee, 0x4a, 0x77, 0x8d, 0x16, 0xef,0x67 };
uint32_t DevAddr = ( uint32_t )0x007e6ae1;
/*LoraWan region, select in arduino IDE tools*/
LoRaMacRegion_t loraWanRegion = LORAWAN_ACTIVE_REGION;
// Initialise (but do not set a value for) the OTAA Keys
uint8_t DevEui[8]={};
uint8_t AppEui[8]={};
uint8_t AppKey[16]={};
uint32_t license[4] = {0x0,0x0,0x0,0x0};
// Create our configuration object
Preferences config;
String wifi_ssid;
String wifi_pass;
String provisioning_uri;
// Heltec need the chip ID for the lorawan license
uint64_t chipId = 0;
/*LoraWan debug level, select in arduino IDE tools.
* None : print basic info.
* Freq : print Tx and Rx freq, DR info.
* Freq && DIO : print Tx and Rx freq, DR, DIO0 interrupt and DIO1 interrupt info.
* Freq && DIO && PW: print Tx and Rx freq, DR, DIO0 interrupt, DIO1 interrupt and MCU deepsleep info.
*/
uint8_t debugLevel = LORAWAN_DEBUG_LEVEL;
// The following functions are taken from
// https://forum.arduino.cc/t/arduino-code-equivalent-for-python-code-binascii-unhexlify-command/674454/3
// and appear to convert the data correctly from the string representation of the TTN API to the uint8_t
// expected by the LoRaWAN library.
// add the value of an hex digit to res
// if the supposed hexDigit is not an hex digit, return false
bool addHexDigitToInt(const char hexDigit, int *res)
{
if (isdigit(hexDigit))
{
*res += (hexDigit - '0');
return true;
}
if (!isxdigit(hexDigit))
return false;
const char base = isupper(hexDigit) ? 'A' : 'a';
*res += (hexDigit - base) + 10;
return true;
}
// convert two hex digits to an integer
// if one (or both) chars are not hex digits, return -1
int twoHexDigitToInt(const char *pHex)
{
int res = 0;
if (!addHexDigitToInt(*pHex, &res))
return -1;
res <<= 4;
if (addHexDigitToInt(*(pHex + 1), &res))
return res;
return -1;
}
// convert the hex representation of a byte array back to a byte array
//
// parameters
//
// hexStr = [input] hex string, must contain only hex digit (['0'-'9']['a'-'f']['A'-'F']), two hex digit per output byte
// binBuf = [output] buffer for binary data
// binBufLen = [input] len of binBuf in bytes, must be >= strlen(hexStr)/2
//
// return
// true if conversion was successfull, false otherwise
//
bool hexToBin(const char *hexStr, byte *binBuf, const int binBufLen)
{
int hexLen = strlen(hexStr);
Serial.print("hexToBin called with string: ");
Serial.print(hexStr);
Serial.print(". Length of string is ");
Serial.print(hexLen);
Serial.print(" and size was set to ");
Serial.println(binBufLen);
if (hexLen % 2) {
// the input string contains an odd number of hex chars
Serial.println("Odd number of hex characters detected");
return false;
}
if (binBufLen < hexLen/2){
Serial.print("Buffer is too small to store the data, must be at least ");
Serial.print(hexLen);
Serial.print(" but size was set to ");
Serial.println(binBufLen);
// the output buffer can't store all the data
return false;
}
const char *pHex = hexStr;
byte *pBuf = binBuf;
for (int i=0; i < hexLen; i+=2)
{
int nextVal = twoHexDigitToInt(pHex+i);
if (nextVal < 0)
return false;
*pBuf = (byte)nextVal;
pBuf++;
}
return true;
}
void setup() {
Serial.begin(115200);
delay(300);
Serial.begin(115200);
while (!Serial);
SPI.begin(SCK,MISO,MOSI,SS);
// Get the chipID values for Heltec
chipId=ESP.getEfuseMac();
Serial.printf("ESP32ChipID=%04X",(uint16_t)(chipId>>32));//print High 2bytes
Serial.printf("%08X\r\n",(uint32_t)chipId);//print Low 4bytes.
// Hook into our preferences
config.begin("wifi", false);
// If we're setting a new WiFi configuration, do it now
if (STR(WIFI_SSID) != ""){
Serial.print("WiFi SSID was not set, setting to ");
Serial.println(STR(WIFI_SSID));
config.putString("wifi_ssid", STR(WIFI_SSID));
config.putString("wifi_pass", STR(WIFI_PASS));
}
wifi_ssid = config.getString("wifi_ssid", "");
wifi_pass = config.getString("wifi_pass", "");
// Save our changes to the configuration
config.end();
// Connect to the WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(wifi_ssid.c_str(), wifi_pass.c_str());
Serial.print("Connecting to ");
Serial.println(wifi_ssid);
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.print("Connected to WiFi. IP Address: ");
Serial.println(WiFi.localIP());
// Get registration details
if ( WiFi.status() == WL_CONNECTED){
Serial.println("Attempting to register with server");
HTTPClient htreg;
htreg.setTimeout(1000);
provisioning_uri = "https://my.provisioning.server/api/path";
Serial.print("Reg URI: ");
Serial.println(provisioning_uri.c_str());
htreg.begin(provisioning_uri.c_str());
// Use HTTP 1.x
htreg.useHTTP10(true);
// Add the headers, including the auth token
htreg.addHeader("Content-Type", "application/json");
// Create the payload JSON doc
DynamicJsonDocument reg_doc(128);
// Get the MAC Address
String mac_addr = WiFi.macAddress();
// Convert it to lower case
mac_addr.toLowerCase();
// Remove the colons
mac_addr.replace(":", "");
// Echo to the serial port
Serial.print("DEVICE ID: ");
Serial.println(mac_addr);
// Set the JSON Value
reg_doc["mac_addr"] = mac_addr;
// Create the payload string
String reg_payload;
// Serialise the payload string into the payload doc
serializeJson(reg_doc, reg_payload);
// Get the status code
int resp_code = htreg.POST(reg_payload);
Serial.print("HTTP Response Code was: ");
Serial.println(resp_code);
// If the status code is 200, then we have a valid template being returned
if(resp_code == 200){
// We have a response.
// This will look like the following and the values are straight from the TTN API:
//
// {
// "dev_eui": "CC50E381ABC41242",
// "app_key": "EFE07F2B338DC55D60CA984E408DE5C3",
// "app_eui": "CC50E381ABC41242"
// }
DynamicJsonDocument ttn_config_response(768);
DeserializationError error = deserializeJson(ttn_config_response, htreg.getStream());
if (error) {
Serial.print("TTN Response: ");
Serial.println(htreg.getString());
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
Serial.println("Configuring for TTN.");
Serial.print("DevEui JSON: ");
Serial.println(ttn_config_response["dev_eui"].as<String>());
Serial.print("AppEui JSON: ");
Serial.println(ttn_config_response["app_eui"].as<String>());
Serial.print("AppKey JSON: ");
Serial.println(ttn_config_response["app_key"].as<String>());
// Convert our "strings" into bytes that can be passed to the library
bool dev_translate = hexToBin(ttn_config_response["dev_eui"], DevEui, 16);
bool app_eui_translate = hexToBin(ttn_config_response["app_eui"], AppEui, 16);
bool app_key_translate = hexToBin(ttn_config_response["app_key"], AppKey, 32);
// Confirm that our translations were successful
if (dev_translate != true){
Serial.println("DevEui Translation failed");
}
if (app_eui_translate != true){
Serial.println("AppEui Translation failed");
}
if (app_key_translate != true){
Serial.println("App Key Translation failed");
}
// Print our values to the command line to ensure they still match the JSON values above
Serial.printf("\nDevEui=");
for(int i = 0;i<sizeof(DevEui);i++)
{
Serial.printf("%02X",DevEui[i]);
}
Serial.printf("\nAppEui=");
for(int i = 0;i<sizeof(AppEui);i++)
{
Serial.printf("%02X",AppEui[i]);
}
Serial.printf("\nAppKey=");
for(int i = 0;i<sizeof(AppKey);i++)
{
Serial.printf("%02X",AppKey[i]);
}
// Start the MCU Init process
if(mcuStarted==0)
{
LoRaWAN.displayMcuInit();
}
Mcu.init(SS,RST_LoRa,DIO0,DIO1,license);
deviceState = DEVICE_STATE_INIT;
}else{
Serial.print("Error on sending POST: ");
Serial.println(resp_code);
}
htreg.end(); //Free resources
}
}
void loop(){
switch( deviceState )
{
case DEVICE_STATE_INIT:
{
Serial.println("LoRaWAN Initialising");
LoRaWAN.init(loraWanClass,loraWanRegion);
break;
}
case DEVICE_STATE_JOIN:
{
Serial.println("Attempting Join");
LoRaWAN.join();
break;
}
case DEVICE_STATE_SEND:
{
Serial.println("Sending Data");
prepareTxFrame( appPort );
LoRaWAN.send(loraWanClass);
deviceState = DEVICE_STATE_CYCLE;
break;
}
case DEVICE_STATE_CYCLE:
{
Serial.println("Cycling State");
// Schedule next packet transmission
txDutyCycleTime = appTxDutyCycle + randr( -APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND );
LoRaWAN.cycle(txDutyCycleTime);
deviceState = DEVICE_STATE_SLEEP;
break;
}
case DEVICE_STATE_SLEEP:
{
Serial.println("Sleeping");
LoRaWAN.sleep(loraWanClass,debugLevel);
break;
}
default:
{
Serial.println("Default Case Triggered, set to reinit");
deviceState = DEVICE_STATE_INIT;
break;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment