Skip to content

Commit

Permalink
Improve LoRaWAN code and other minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
pjalocha committed Jan 16, 2022
1 parent d9502ab commit 48063e3
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 167 deletions.
49 changes: 26 additions & 23 deletions main/disp_oled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,18 @@ void OLED_DrawBaro(u8g2_t *OLED, GPS_Position *GPS)
u8g2_DrawStr(OLED, 0, 48, Line);
}

static uint8_t BattCapacity(uint16_t mVolt)
{ if(mVolt>=4100) return 100;
if(mVolt<=3600) return 0;
return (mVolt-3600+2)/5; }
static int8_t BattCapacity(uint16_t mVolt) // deduce battery capacity from its voltage
{ if(mVolt>=4100) return 100; // if 4.1V or more then full
if(mVolt<=1000) return -1; // if below 1.0V then no-battery
if(mVolt<=3600) return 0; // if below 3.6V then empty
return (mVolt-3600+2)/5; } // otherwise a linear function from 3.6V to 4.1V

void OLED_DrawBattery(u8g2_t *OLED, GPS_Position *GPS) // draw battery status page
{
#ifdef WITH_MAVLINK
uint8_t Cap=MAVLINK_BattCap; // [%] from the drone's telemetry
int8_t Cap=MAVLINK_BattCap; // [%] from the drone's telemetry
#else
uint8_t Cap=BattCapacity(BatteryVoltage>>8); // [%] est. battery capacity based on the voltage readout
int8_t Cap=BattCapacity(BatteryVoltage>>8); // [%] est. battery capacity based on the voltage readout
#endif
// u8g2_SetFont(OLED, u8g2_font_battery19_tn);
// u8g2_DrawGlyph(OLED, 120, 60, '0'+(Cap+10)/20);
Expand All @@ -486,13 +487,14 @@ void OLED_DrawBattery(u8g2_t *OLED, GPS_Position *GPS) // draw battery status pa

u8g2_SetFont(OLED, u8g2_font_9x15_tr);

strcpy(Line, " %");
if(Cap>=100) Format_UnsDec(Line, Cap, 3);
else if(Cap>=10) Format_UnsDec(Line+1, Cap, 2);
else Line[2]='0'+Cap;
u8g2_DrawStr (OLED, 16, 32, Line); // print battery est. capacity
u8g2_DrawFrame(OLED, 12, 20, 42, 14); // draw battery empty box around it
u8g2_DrawBox (OLED, 8, 23, 4, 8); // and the battery tip
if(Cap>=0)
{ strcpy(Line, " %");
if(Cap>=100) Format_UnsDec(Line, (uint8_t)Cap, 3);
else if(Cap>=10) Format_UnsDec(Line+1, (uint8_t)Cap, 2);
else Line[2]='0'+Cap;
u8g2_DrawStr (OLED, 16, 32, Line); // print battery est. capacity
u8g2_DrawFrame(OLED, 12, 20, 42, 14); // draw battery empty box around it
u8g2_DrawBox (OLED, 8, 23, 4, 8); } // and the battery tip

strcpy(Line, " . V");
#ifdef WITH_MAVLINK
Expand Down Expand Up @@ -555,9 +557,9 @@ void OLED_DrawBattery(u8g2_t *OLED, GPS_Position *GPS) // draw battery status pa
void OLED_DrawStatusBar(u8g2_t *OLED, GPS_Position *GPS) // status bar on top of the OLED
{ static bool Odd=0;
#ifdef WITH_MAVLINK
uint8_t Cap = MAVLINK_BattCap; // [%]
int8_t Cap = MAVLINK_BattCap; // [%]
#else
uint8_t Cap = BattCapacity(BatteryVoltage>>8); // [%] est. battery capacity
int8_t Cap = BattCapacity(BatteryVoltage>>8); // [%] est. battery capacity
#endif
uint8_t BattLev = (Cap+10)/20; // [0..5] convert to display scale
uint8_t Charging = 0; // charging or not changing ?
Expand All @@ -578,14 +580,15 @@ void OLED_DrawStatusBar(u8g2_t *OLED, GPS_Position *GPS) // status bar on top
#else
uint8_t &DispLev = BattLev;
#endif
if(BattLev==0 && !Charging && Odd) // when battery is empty, then flash it at 0.5Hz
{ } // thus here avoid printing the battery symbol for flashing effect
else // print the battery symbol with DispLev
{ u8g2_SetFont(OLED, u8g2_font_battery19_tn);
u8g2_SetFontDirection(OLED, 3);
u8g2_DrawGlyph(OLED, 20, 10, '0'+DispLev);
u8g2_SetFontDirection(OLED, 0); }
Odd=!Odd;
if(Cap>=0)
{ if(BattLev==0 && !Charging && Odd) // when battery is empty, then flash it at 0.5Hz
{ } // thus here avoid printing the battery symbol for flashing effect
else // print the battery symbol with DispLev
{ u8g2_SetFont(OLED, u8g2_font_battery19_tn);
u8g2_SetFontDirection(OLED, 3);
u8g2_DrawGlyph(OLED, 20, 10, '0'+DispLev);
u8g2_SetFontDirection(OLED, 0); }
Odd=!Odd; }
#ifdef WITH_SD
if(SD_isMounted())
{ u8g2_SetFont(OLED, u8g2_font_twelvedings_t_all);
Expand Down
39 changes: 31 additions & 8 deletions main/fanet.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ class FANET_Packet
if(Pref==0x08 || Pref==0x11 || Pref==0x20 || Pref==0xDD || Pref==0xDE || Pref==0xDF) return 2;
return 3; }

void setAddress(uint32_t Addr) { setAddrPref(Addr>>16); setAddrLow(Addr); }
void setAddrPref(uint8_t Prefix) { Byte[1]=Prefix; }
void setAddrLow(uint16_t Addr ) { Byte[2]=Addr; Byte[3]=Addr>>8; }
void setAddress(uint32_t Addr) { setAddrPref(Addr>>16); setAddrLow(Addr); } // full 24-bit address
void setAddrPref(uint8_t Prefix) { Byte[1]=Prefix; } // address prefix
void setAddrLow(uint16_t Addr ) { Byte[2]=Addr; Byte[3]=Addr>>8; } // lower 16-bits of the address
void setHeader(uint8_t Type) { Byte[0] = 0x40 | (Type&0x3F); }
void setType(uint8_t Type) { Byte[0] = (Byte[0]&0xC0) | (Type&0x3F); }
void setType(uint8_t Type) { Byte[0] = (Byte[0]&0xC0) | (Type&0x3F); } // packet-type: 1=air-position

uint8_t ExtHeaderLen(void) const // length ot the extended header (zero in most cases)
{ if(!ExtHeader()) return 0;
Expand All @@ -62,7 +62,8 @@ class FANET_Packet

uint8_t MsgOfs(void) const { return 4+ExtHeaderLen(); } // offset to the actual message (past the header and ext. header)
uint8_t MsgLen(void) const { return Len-4-ExtHeaderLen(); } // length of the actual message
const uint8_t *Msg(void) const { return Byte+MsgOfs(); }
const uint8_t *Msg(void) const { return Byte+MsgOfs(); } // pointer to the message, past the header
uint8_t *Msg(void) { return Byte+MsgOfs(); }

void setName(const char *Name)
{ setHeader(2);
Expand Down Expand Up @@ -325,7 +326,7 @@ class FANET_RxPacket: public FANET_Packet
printf("%s CR%c%c%c %3.1fdB/%de %+3.1fkHz ", HHMMSS, '0'+CR, hasCRC?'c':'_', badCRC?'-':'+', 0.25*SNR, BitErr, 1e-2*FreqOfs);
FANET_Packet::Print(Name); }

int WriteJSON(char *JSON) const
int WriteStxJSON(char *JSON) const
{ int Len=0;
Len+=Format_String(JSON+Len, "\"addr\":\"");
Len+=Format_Hex(JSON+Len, Byte[1]);
Expand Down Expand Up @@ -378,7 +379,29 @@ class FANET_RxPacket: public FANET_Packet
Len+=Format_String(JSON+Len, ",\"lat_deg\":");
Len+=Format_SignDec(JSON+Len, CoordUBX(Lat), 8, 7, 1);
Len+=Format_String(JSON+Len, ",\"lon_deg\":");
Len+=Format_SignDec(JSON+Len, CoordUBX(Lon), 8, 7, 1); }
Len+=Format_SignDec(JSON+Len, CoordUBX(Lon), 8, 7, 1);
int Idx=7;
if(Service&0x40)
{ Len+=Format_String(JSON+Len, ",\"temp_deg\":");
Len+=Format_SignDec(JSON+Len, (int16_t)5*((int8_t)Msg[Idx++]), 2, 1, 1); }
if(Service&0x20)
{ uint16_t Dir = Msg[Idx++]; // [cordic]
Len+=Format_String(JSON+Len, ",\"wind_deg\":");
Len+=Format_UnsDec(JSON+Len, (45*Dir+16)>>5, 2, 1);
uint16_t Wind = getSpeed(Msg[Idx++]); // [0.2km/h]
Len+=Format_String(JSON+Len, ",\"wind_kmh\":");
Len+=Format_UnsDec(JSON+Len, 2*Wind, 2, 1);
uint16_t Gust = getSpeed(Msg[Idx++]);
Len+=Format_String(JSON+Len, ",\"gust_kmh\":");
Len+=Format_UnsDec(JSON+Len, 2*Gust, 2, 1); }
if(Service&0x10)
{ Len+=Format_String(JSON+Len, ",\"hum_perc\":");
Len+=Format_UnsDec(JSON+Len, (uint16_t)4*Msg[Idx++], 2, 1); }
if(Service&0x08)
{ Len+=Format_String(JSON+Len, ",\"press_hpa\":");
Len+=Format_UnsDec(JSON+Len, getPressure(Msg+Idx), 2, 1);
Idx+=2; }
}
if(Type==1 || Type==7) // airborne or ground position
{ int32_t Lat = getLat(Msg); // [cordic] decode the latitude
int32_t Lon = getLon(Msg+3); // [cordic] decode the longitude
Expand Down Expand Up @@ -568,7 +591,7 @@ class FANET_RxPacket: public FANET_Packet
}
if(SNR>0)
{ Out[Len++]=' ';
Len+=Format_UnsDec(Out+Len, ((uint16_t)SNR*10+2)/4, 2, 1);
Len+=Format_SignDec(Out+Len, ((int16_t)SNR*10+2-843)/4, 2, 1, 1);
Out[Len++]='d'; Out[Len++]='B'; }
Out[Len++]=' ';
Len+=Format_SignDec(Out+Len, FreqOfs/10, 2, 1);
Expand Down
156 changes: 106 additions & 50 deletions main/lorawan.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,68 +13,100 @@

class LoRaWANnode
{ public:
static const uint8_t Chans=8;
uint64_t AppEUI; // from application registration: Application identification
uint8_t AppKey[16]; // from device registration: application encryption/decryption key
uint64_t DevEUI; // Device identification (MAC)
uint32_t DevNonce; // unique counter kept by the device for Join-Requests
uint8_t NetSesKey[16]; // from Join-Accept: Network Session Key
uint8_t AppSesKey[16]; // from Join-Accept: App Session Key
uint32_t JoinNonce; // from Join-Accept: unique must not be reused
uint32_t HomeNetID; // from Join-Accept: Home Network ID
uint32_t DevAddr; // from Join-Accept: Device Address
uint8_t DLsetting; // from Join-Accept: DownLink configuration: OptNeg:1 | RX1 data rate offset:3 | RX2 data rate:4
uint8_t RxDelay; // from Join-Accept: RFU:4 | Del:4 Del=1..15s for the RX1, RX2 delay is Del+1
uint8_t State; // 0:disconencted, 1:join-request sent, 2:join-accept received, 3:uplink-packet sent
uint8_t Chan; // [0..7] Current channel being used
uint32_t UpCount; // [seq] Uplink frame counter: reset when joining the network
uint32_t DnCount; // [seq] Downlink frame counter: reset when joining the network
uint32_t TxCount; // [packets] transmitted to the network
uint32_t LastTx; // [sec] last transmission
uint32_t RxCount; // [packets] received from the network
uint32_t LastRx; // [sec] when last heard from the network
int8_t RxSNR; // [0.25dB] SNR on receive
int8_t RxRSSI; // [dBm] signal strength
union
{ uint8_t Flags;
struct
{ bool RxACK :1; // received ACK
bool TxACK :1; // ACK to be transmitted
bool RxPend:1; // more frames pending for reception
} ;
} ;
uint8_t Spare;
uint8_t Packet[40]; // generic packet for storage/processing
static const uint8_t Chans = 8;
static const size_t UsedBytes = 112; // [bytes] actually used
static const size_t SaveBytes = 128; // [bytes] round up and including CRC32
static const size_t SaveWords = SaveBytes/4;

union
{ uint8_t Byte[SaveBytes];
uint32_t Word[SaveWords];
struct
{ uint64_t AppEUI; // from application registration: Application identification
uint8_t AppKey[16]; // from device registration: application encryption/decryption key
uint64_t DevEUI; // Device identification (MAC)
uint32_t DevNonce; // unique counter kept by the device for Join-Requests
// the four items above need to be kept stored permanently per each device
// the other elements below are obtained when join-accept is received from the LoRaWAN network
uint8_t NetSesKey[16]; // from Join-Accept: Network Session Key
uint8_t AppSesKey[16]; // from Join-Accept: App Session Key
uint32_t JoinNonce; // from Join-Accept: unique must not be reused
uint32_t HomeNetID; // from Join-Accept: Home Network ID
uint32_t DevAddr; // from Join-Accept: Device Address
uint8_t DLsetting; // from Join-Accept: DownLink configuration: OptNeg:1 | RX1 data rate offset:3 | RX2 data rate:4
uint8_t RxDelay; // from Join-Accept: RFU:4 | Del:4 Del=1..15s for the RX1, RX2 delay is Del+1
uint8_t State; // 0:disconencted, 1:join-request sent, 2:join-accept received, 3:uplink-packet sent
uint8_t Chan; // [0..7] Current channel being used
uint32_t UpCount; // [seq] Uplink frame counter: reset when joining the network
uint32_t DnCount; // [seq] Downlink frame counter: reset when joining the network
uint32_t TxCount; // [packets] transmitted to the network
uint32_t LastTx; // [sec] last transmission
uint32_t RxCount; // [packets] received from the network
uint32_t LastRx; // [sec] when last heard from the network
int8_t RxSNR; // [0.25dB] SNR on receive (can be negative)
int8_t RxRSSI; // [dBm] signal strength
union
{ uint8_t Flags;
struct
{ bool RxACK :1; // received ACK
bool TxACK :1; // ACK to be transmitted
bool RxPend:1; // more frames pending for reception
bool Enable:1; // Enable/disable operation
bool SaveReq:1; // Request to save the node state
} ;
} ;
uint8_t Spare; // 112 bytes up to this point
uint32_t LastSaved; // [sec] when saved to EEPROM or other permament storage
uint8_t Dummy[8]; // just to fill up the space, could be used later
uint32_t CRC32; // 128 bytes up to here: fits into 1kbit EEPROM
} ;
} ;

// uint8_t TxMAC[16];
// uint8_t TxMACs;
static const size_t MaxPacketSize = 64; // [bytes]
uint8_t Packet[MaxPacketSize]; // generic packet for storage/processing

public:
LoRaWANnode() { Reset(); }

void Reset(void)
{ State=0; DevNonce=0; JoinNonce=0;
LastTx=0; TxCount=0; LastRx=0; RxCount=0; Flags=0; }
LastTx=0; TxCount=0; LastRx=0; RxCount=0; Flags=0; LastSaved=0;
setCRC(); }

void Reset(uint64_t MAC, uint8_t *AppKey=0)
{ AppEUI=0x70B3D57ED0035895;
DevEUI=MAC;
if(AppKey) memcpy(this->AppKey, AppKey, 16);
Reset(); }
{ AppEUI=0x70B3D57ED0035895; // set OGN application
DevEUI=MAC; // set DevEUI from MAC
if(AppKey) memcpy(this->AppKey, AppKey, 16); // set the AppKey
Reset(); } // reset to not-joined state

void Disconnect(void)
{ State=0; }

uint8_t incrChan(uint8_t Step=1) { Chan+=Step; if(Chan>=Chans) Chan-=Chans; return Chan; }
uint32_t calcCRC(void) const
{ uint32_t Sum=0x87654321; // start the sum with some magic
for(size_t Idx=0; Idx<SaveWords; Idx++)
Sum+=Word[Idx]; // sum all the Words
return Sum; } // return the Sum

bool goodCRC(void) const { return calcCRC()==0; }
void setCRC(void) { CRC32=0; CRC32 = CRC32-calcCRC(); }

int Save(FILE *File) { return fwrite(this, sizeof(LoRaWANnode), 1, File); }
int Save(const char *FileName)
uint8_t incrChan(uint8_t Step=1)
{ Chan+=Step; if(Chan>=Chans) Chan-=Chans; return Chan; }

int Save(FILE *File) { return fwrite(this, sizeof(LoRaWANnode), 1, File); } // save to a file
int Save(const char *FileName) // save to a file
{ FILE *File=fopen(FileName, "wb"); if(File==0) return 0;
int Written=Save(File); fclose(File); return Written; }

int Restore(FILE *File) { return fread(this, sizeof(LoRaWANnode), 1, File); }
int Restore(const char *FileName)
{ FILE *File=fopen(FileName, "rb"); if(File==0) return 0;
int Read=Restore(File); fclose(File); return Read; }
// int Restore(FILE *File) { return fread(this, sizeof(LoRaWANnode), 1, File); }
// int Restore(const char *FileName)
// { FILE *File=fopen(FileName, "rb"); if(File==0) return 0;
// int Read=Restore(File); fclose(File); return Read; }

static int ReadHex(uint8_t *Data, int Len, const char *Inp)
static int ReadHex(uint8_t *Data, int Len, const char *Inp) // read Len bytes from a hex string
{ int Bytes=0;
for( ; Bytes<Len; )
{ int8_t H = Read_Hex1(*Inp++); if(H<0) break;
Expand All @@ -83,7 +115,7 @@ class LoRaWANnode
return Bytes; }

// int readAppEUI(const char *Inp) { return ReadHex(&AppEUI, 8, Inp); }
int readAppKey(const char *Inp) { return ReadHex(AppKey,16, Inp); }
int readAppKey(const char *Inp) { return ReadHex(AppKey,16, Inp); } // read key from the hex string
// int readDevEUI(const char *Inp) { return ReadHex(&DevEUI, 8, Inp); }

template<class Type>
Expand Down Expand Up @@ -168,15 +200,18 @@ class LoRaWANnode
LoRaMacPayloadEncrypt(Data, DataLen, AppSesKey, DevAddr, 0, UpCount, Packet+PktLen); PktLen+=DataLen; // copy+encrypt user data
uint32_t MIC=0;
LoRaMacComputeMic(Packet, PktLen, NetSesKey, DevAddr, 0x00, UpCount, &MIC); // calc. MIC
// uint8_t MIC2[4];
// Tiny.Calculate_MIC(Packet, MIC2, PktLen, UpCount, 0x00);
// printf("Data packet MIC: %08X <=> %02X%02X%02X%02X\n", MIC, MIC2[3], MIC2[2], MIC2[1], MIC2[0]);
memcpy(Packet+PktLen, &MIC, 4); PktLen+=4; // append MIC
UpCount++; State=3; return PktLen; } // return the packet size

int getDataPacket(uint8_t **Pkt, const uint8_t *Data, int DataLen, uint8_t Port=1, bool Confirm=0)
{ int Len=getDataPacket(Packet, Data, DataLen, Port, Confirm); *Pkt = Packet; return Len; }

int procRxData(const RFM_LoRa_RxPacket &RxPacket)
{ int Ret = procRxData(RxPacket.Byte, RxPacket.Len); if(Ret<0) return Ret;
RxSNR += (RxPacket.SNR-RxSNR+1)/2; // if good packet then update the signal statistics
{ int Ret=procRxData(RxPacket.Byte, RxPacket.Len); if(Ret<0) return Ret;
RxSNR += (RxPacket.SNR-RxSNR+1)/2;
RxRSSI += (RxPacket.RSSI-RxRSSI+1)/2;
return Ret; }

Expand All @@ -191,6 +226,7 @@ class LoRaWANnode
if(CountDiff<=0) return -1; // attempt to reuse the counter: drop this packet
uint32_t MIC=0;
LoRaMacComputeMic(PktData, PktLen-4, NetSesKey, Addr, 0x01, Count, &MIC);
// printf("RxData: %08X\n", MIC);
if(memcmp(PktData+PktLen-4, &MIC, 4)) return -1; // give up if MIC does not match
uint8_t OptLen = Ctrl&0x0F; // Options: how many bytes
uint8_t DataOfs = 8 + OptLen; // where the port byte should be
Expand Down Expand Up @@ -224,12 +260,22 @@ class LoRaWANnode
// Format_Hex(CONS_UART_Write, Opt[Idx]);
// Format_String(CONS_UART_Write, "\n"); }

int WriteToFile(const char *Name)
{ FILE *File = fopen(Name, "wb"); if(File==0) return -1;
int Written = fwrite(this, 1, SaveBytes, File);
fclose(File); return Written; }

int ReadFromFile(const char *Name)
{ FILE *File = fopen(Name, "rb"); if(File==0) return -1;
int Read = fread(this, 1, SaveBytes, File);
fclose(File); return Read; }

#ifdef WITH_ESP32
esp_err_t WriteToNVS(const char *Name="LoRaWAN", const char *NameSpace="TRACKER")
{ nvs_handle Handle;
esp_err_t Err = nvs_open(NameSpace, NVS_READWRITE, &Handle);
if(Err!=ESP_OK) return Err;
Err = nvs_set_blob(Handle, Name, this, sizeof(LoRaWANnode)-40);
Err = nvs_set_blob(Handle, Name, this, SaveBytes);
if(Err==ESP_OK) Err = nvs_commit(Handle);
nvs_close(Handle);
return Err; }
Expand All @@ -240,12 +286,22 @@ class LoRaWANnode
if(Err!=ESP_OK) return Err;
size_t Size=0;
Err = nvs_get_blob(Handle, Name, 0, &Size); // get the Size of the blob in the Flash
if( (Err==ESP_OK) && (Size<=(sizeof(LoRaWANnode)-40)) )
if( (Err==ESP_OK) && (Size<=SaveBytes) )
Err = nvs_get_blob(Handle, Name, this, &Size); // read the Blob from the Flash
nvs_close(Handle);
return Err; }
#endif // WITH_ESP32

#ifdef RTLSDR_API
int Read(RTLSDR &SDR) { return SDR.readEEPROM(Byte, 0x80, SaveBytes); }
int Write(RTLSDR &SDR) { return SDR.writeEEPROM(Byte, 0x80, SaveBytes); }
#endif

bool isFF(void)
{ for(size_t Idx=0; Idx<SaveBytes; Idx++)
{ if(Byte[Idx]!=0xFF) return 0; }
return 1; }

} ;

#endif // __LORAWAN_H__
Loading

0 comments on commit 48063e3

Please sign in to comment.