#include #include #include #include #include // #include "esp_system.h" // #include "esp_sleep.h" #include "hal.h" #include "sens.h" #include "rf.h" #include "ctrl.h" #include "proc.h" #ifdef WITH_STRATUX #include "stratux.h" #endif #ifdef WITH_WIFI #include "wifi.h" #endif #ifdef WITH_AP #include "wifi.h" #endif #ifdef WITH_BT_SPP #include "bt.h" #endif #include "gps.h" // #include "ubx.h" // #include "timesync.h" #include "format.h" #include "config.h" #define QUOTE(name) #name #define STR(macro) QUOTE(macro) #ifndef VERSION #define VERSION 0.1.6 #endif static char Line[128]; // ======================================================================================================================== #ifdef WITH_OLED #include "disp_oled.h" int OLED_DisplayStatus(uint32_t Time, uint8_t LineIdx) { Format_String(Line , "OGN Tx/Rx "); Format_HHMMSS(Line+10, Time); OLED_PutLine(LineIdx++, Line); Parameters.Print(Line); OLED_PutLine(LineIdx++, Line); return 0; } int OLED_DisplayPosition(GPS_Position *GPS=0, uint8_t LineIdx=2) { if(GPS && GPS->isValid()) { Line[0]=' '; Format_SignDec(Line+1, GPS->Latitude /60, 6, 4); Line[9]=' '; Format_UnsDec (Line+10, GPS->Altitude /10, 5, 0); Line[15]='m'; OLED_PutLine(LineIdx , Line); Format_SignDec(Line, GPS->Longitude/60, 7, 4); Format_SignDec(Line+10, GPS->ClimbRate, 4, 1); OLED_PutLine(LineIdx+1, Line); Format_UnsDec (Line , GPS->Speed, 4, 1); Format_String(Line+5, "m/s "); Format_UnsDec (Line+10, GPS->Heading, 4, 1); Line[15]='^'; OLED_PutLine(LineIdx+2, Line); Format_String(Line, "0D/00sat DOP00.0"); Line[0]+=GPS->FixMode; Format_UnsDec(Line+3, GPS->Satellites, 2); Format_UnsDec(Line+12, (uint16_t)GPS->HDOP, 3, 1); OLED_PutLine(LineIdx+3, Line); } else { OLED_PutLine(LineIdx, 0); OLED_PutLine(LineIdx+1, 0); OLED_PutLine(LineIdx+2, 0); OLED_PutLine(LineIdx+3, 0); } if(GPS && GPS->isDateValid()) { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; Line[9]=' '; } else Format_String(Line, " "); if(GPS && GPS->isTimeValid()) { Format_UnsDec (Line+10, (uint16_t)GPS->Hour, 2, 0); Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); Format_UnsDec (Line+14, (uint16_t)GPS->Sec, 2, 0); } else Line[10]=0; OLED_PutLine(LineIdx+4, Line); Line[0]=0; if(GPS && GPS->hasBaro) { Format_String(Line , "0000.0hPa 00000m"); Format_UnsDec(Line , GPS->Pressure/40, 5, 1); Format_UnsDec(Line+10, GPS->StdAltitude/10, 5, 0); } OLED_PutLine(LineIdx+5, Line); return 0; } #endif // ======================================================================================================================== #ifdef WITH_U8G2_OLED void OLED_DrawLogo(u8g2_t *OLED, GPS_Position *GPS) // draw logo and hardware options in software { u8g2_DrawCircle(OLED, 96, 32, 30, U8G2_DRAW_ALL); u8g2_DrawCircle(OLED, 96, 32, 34, U8G2_DRAW_UPPER_RIGHT); u8g2_DrawCircle(OLED, 96, 32, 38, U8G2_DRAW_UPPER_RIGHT); // u8g2_SetFont(OLED, u8g2_font_open_iconic_all_4x_t); // u8g2_DrawGlyph(OLED, 64, 32, 0xF0); u8g2_SetFont(OLED, u8g2_font_ncenB14_tr); u8g2_DrawStr(OLED, 74, 31, "OGN"); u8g2_SetFont(OLED, u8g2_font_8x13_tr); u8g2_DrawStr(OLED, 69, 43, "Tracker"); #ifdef WITH_FollowMe u8g2_DrawStr(OLED, 0, 16 ,"FollowMe"); #endif #ifdef WITH_TTGO u8g2_DrawStr(OLED, 0, 16 ,"TTGO"); #endif #if defined(WITH_HELTEC) || defined(WITH_HELTEC_V2) u8g2_DrawStr(OLED, 0, 16 ,"HELTEC"); #endif #if defined(WITH_TBEAM) || defined(WITH_TBEAM_V10) u8g2_DrawStr(OLED, 0, 16 ,"T-BEAM"); #endif #ifdef WITH_GPS_MTK u8g2_DrawStr(OLED, 0, 28 ,"MTK GPS"); #endif #ifdef WITH_GPS_UBX u8g2_DrawStr(OLED, 0, 28 ,"UBX GPS"); #endif #ifdef WITH_GPS_SRF u8g2_DrawStr(OLED, 0, 28 ,"SRF GPS"); #endif #ifdef WITH_RFM95 u8g2_DrawStr(OLED, 0, 40 ,"RFM95"); #endif #ifdef WITH_RFM69 u8g2_DrawStr(OLED, 0, 40 ,"RFM69"); #endif #ifdef WITH_BMP180 u8g2_DrawStr(OLED, 0, 52 ,"BMP180"); #endif #ifdef WITH_BMP280 u8g2_DrawStr(OLED, 0, 52 ,"BMP280"); #endif #ifdef WITH_BME280 u8g2_DrawStr(OLED, 0, 52 ,"BME280"); #endif #ifdef WITH_MS5607 u8g2_DrawStr(OLED, 0, 52 ,"MS5607"); #endif #ifdef WITH_MS5611 u8g2_DrawStr(OLED, 0, 52 ,"MS5611"); #endif #ifdef WITH_BT_SPP u8g2_DrawStr(OLED, 0, 64 ,"BT SPP"); #endif } void OLED_PutLine(u8g2_t *OLED, uint8_t LineIdx, const char *Line) { if(Line==0) return; #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, "OLED_PutLine( ,"); Format_UnsDec(CONS_UART_Write, (uint16_t)LineIdx); CONS_UART_Write(','); Format_String(CONS_UART_Write, Line); Format_String(CONS_UART_Write, ")\n"); xSemaphoreGive(CONS_Mutex); #endif // u8g2_SetFont(OLED, u8g2_font_5x8_tr); u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); u8g2_DrawStr(OLED, 0, (LineIdx+1)*8, Line); } void OLED_DrawStatus(u8g2_t *OLED, uint32_t Time, uint8_t LineIdx=0) { Format_String(Line , "OGN Tx/Rx "); Format_HHMMSS(Line+10, Time); Line[16]=0; OLED_PutLine(OLED, LineIdx++, Line); Parameters.Print(Line); Line[16]=0; OLED_PutLine(OLED, LineIdx++, Line); } void OLED_DrawPosition(u8g2_t *OLED, GPS_Position *GPS=0, uint8_t LineIdx=2) { if(GPS && GPS->isValid()) { Line[0]=' '; Format_SignDec(Line+1, GPS->Latitude /60, 6, 4); Line[9]=' '; Format_UnsDec (Line+10, GPS->Altitude /10, 5, 0); Line[15]='m'; OLED_PutLine(OLED, LineIdx , Line); Format_SignDec(Line, GPS->Longitude/60, 7, 4); Format_SignDec(Line+10, GPS->ClimbRate, 4, 1); OLED_PutLine(OLED, LineIdx+1, Line); Format_UnsDec (Line , GPS->Speed, 4, 1); Format_String(Line+5, "m/s "); Format_UnsDec (Line+10, GPS->Heading, 4, 1); Line[15]='^'; OLED_PutLine(OLED, LineIdx+2, Line); Format_String(Line, "0D/00sat DOP00.0"); Line[0]+=GPS->FixMode; Format_UnsDec(Line+3, GPS->Satellites, 2); Format_UnsDec(Line+12, (uint16_t)GPS->HDOP, 3, 1); OLED_PutLine(OLED, LineIdx+3, Line); } // else { OLED_PutLine(OLED, LineIdx, 0); OLED_PutLine(OLED, LineIdx+1, 0); OLED_PutLine(LineIdx+2, 0); OLED_PutLine(LineIdx+3, 0); } if(GPS && GPS->isDateValid()) { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; Line[9]=' '; } else Format_String(Line, " "); if(GPS && GPS->isTimeValid()) { Format_UnsDec (Line+10, (uint16_t)GPS->Hour, 2, 0); Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); Format_UnsDec (Line+14, (uint16_t)GPS->Sec, 2, 0); } else Line[10]=0; OLED_PutLine(OLED, LineIdx+4, Line); Line[0]=0; if(GPS && GPS->hasBaro) { Format_String(Line , "0000.0hPa 00000m"); Format_UnsDec(Line , GPS->Pressure/40, 5, 1); Format_UnsDec(Line+10, GPS->StdAltitude/10, 5, 0); } OLED_PutLine(OLED, LineIdx+5, Line); } void OLED_DrawGPS(u8g2_t *OLED, GPS_Position *GPS) // GPS time, position, altitude { bool isAltitudeUnitMeter = Parameters.AltitudeUnit == 0; // display altitude in meters bool isAltitudeUnitFeet = Parameters.AltitudeUnit == 1; // display altitude in feet // u8g2_SetFont(OLED, u8g2_font_ncenB14_tr); u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line uint8_t Len=0; /* Len+=Format_String(Line+Len, "GPS "); if(GPS && GPS->isValid()) { Line[Len++]='0'+GPS->FixMode; Line[Len++]='D'; Line[Len++]='/'; Len+=Format_UnsDec(Line+Len, GPS->Satellites, 1); Len+=Format_String(Line+Len, "sat DOP"); Len+=Format_UnsDec(Line+Len, (uint16_t)GPS->HDOP, 2, 1); } else { Len+=Format_String(Line+Len, "(no lock)"); } Line[Len]=0; u8g2_DrawStr(OLED, 0, 12, Line); */ if(GPS && GPS->isDateValid()) { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; } else Format_String(Line, " . . "); if(GPS && GPS->isTimeValid()) { Format_UnsDec (Line+ 9, (uint16_t)GPS->Hour, 2, 0); Line[11]=':'; Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); Line[14]=':'; Format_UnsDec (Line+15, (uint16_t)GPS->Sec, 2, 0); } else Format_String(Line+9, " : : "); Line[17]=0; u8g2_DrawStr(OLED, 0, 24, Line); Len=0; Len+=Format_String(Line+Len, "Lat: "); if(GPS && GPS->isValid()) { Len+=Format_SignDec(Line+Len, GPS->Latitude /6, 7, 5); Line[Len++]=0xB0; } else Len+=Format_String(Line+Len, "---.-----"); Line[Len]=0; u8g2_DrawStr(OLED, 0, 36, Line); Len=0; Len+=Format_String(Line+Len, "Lon: "); if(GPS && GPS->isValid()) { Len+=Format_SignDec(Line+Len, GPS->Longitude /6, 8, 5); Line[Len++]=0xB0; } else Len+=Format_String(Line+Len, "----.-----"); Line[Len]=0; u8g2_DrawStr(OLED, 0, 48, Line); Len=0; Len+=Format_String(Line+Len, "Alt: "); if(GPS && GPS->isValid()) { int32_t Alt = GPS->Altitude; if(Alt>=0) Line[Len++]=' '; if(isAltitudeUnitMeter) // display altitude in meters { Len+=Format_SignDec(Line+Len, Alt, 1, 1, 1); // [0.1m] Line[Len++]='m'; } else if(isAltitudeUnitFeet) // display altitude in feet { Alt = (Alt*336+512)>>10; // [0.1m] => [feet] Len+=Format_SignDec(Line+Len, Alt, 1, 0, 1); // [feet] Line[Len++]='f'; Line[Len++]='t'; } // for( ; Len<14; ) Line[Len++]=' '; // tail of spaces to cover older printouts } else Len+=Format_String(Line+Len, "-----.- "); Line[Len]=0; u8g2_DrawStr(OLED, 0, 60, Line); } void OLED_DrawRF(u8g2_t *OLED, GPS_Position *GPS) // RF { u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines. 12 pixels/line uint8_t Len=0; #ifdef WITH_RFM69 Len+=Format_String(Line+Len, "RFM69"); // Type of RF chip used if(Parameters.RFchipTypeHW) Line[Len++]='H'; Line[Len++]='W'; #endif #ifdef WITH_RFM95 Len+=Format_String(Line+Len, "RFM95"); #endif #ifdef WITH_SX1262 Len+=Format_String(Line+Len, "SX1262"); #endif #ifdef WITH_SX1272 Len+=Format_String(Line+Len, "SX1272"); #endif Line[Len++]=':'; Len+=Format_SignDec(Line+Len, (int16_t)Parameters.TxPower); // Tx power Len+=Format_String(Line+Len, "dBm"); Line[Len++]=' '; Len+=Format_SignDec(Line+Len, (int32_t)Parameters.RFchipFreqCorr, 2, 1); // frequency correction Len+=Format_String(Line+Len, "ppm"); Line[Len]=0; u8g2_DrawStr(OLED, 0, 24, Line); Len=0; Len+=Format_String(Line+Len, "Rx:"); // Len+=Format_SignDec(Line+Len, -5*TRX.averRSSI, 2, 1); // noise level seen by the receiver Len+=Format_String(Line+Len, "dBm "); Len+=Format_UnsDec(Line+Len, RX_OGN_Count64); // received packet/min Len+=Format_String(Line+Len, "/min"); Line[Len]=0; u8g2_DrawStr(OLED, 0, 36, Line); Len=0; Len+=Format_SignDec(Line+Len, (int16_t)TRX.chipTemp); // RF chip internal temperature (not calibrated) // Line[Len++]=0xB0; // Line[Len++]='C'; Len+=Format_String(Line+Len, "\260C RxFIFO:"); Len+=Format_UnsDec(Line+Len, RF_RxFIFO.Full()); // how many packets wait in the RX queue Line[Len]=0; u8g2_DrawStr(OLED, 0, 48, Line); // u8g2_DrawStr(OLED, 0, 48, RF_FreqPlan.getPlanName()); Len=0; Len+=Format_String(Line+Len, RF_FreqPlan.getPlanName()); // name of the frequency plan Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, (uint16_t)(RF_FreqPlan.getCenterFreq()/100000), 3, 1); // center frequency Len+=Format_String(Line+Len, "MHz"); Line[Len]=0; u8g2_DrawStr(OLED, 0, 60, Line); } void OLED_DrawRelay(u8g2_t *OLED, GPS_Position *GPS) { const char *AcftTypeName[16] = { "----", "Glid", "Tow ", "Heli", "SkyD", "Drop", "Hang", "Para", "Pwrd", "Jet ", "UFO ", "Ball", "Zepp", "UAV ", "Car ", "Fix " } ; // u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); // uint8_t Len=Format_String(Line, "Relay:"); // if(GPS && GPS->Sec>=0) { Len+=Format_UnsDec(Line+Len, (uint16_t)(GPS->Sec), 2); Line[Len++]='s'; } // Line[Len]=0; // u8g2_DrawStr(OLED, 0, 24, Line); uint8_t LineIdx=1; for( uint8_t Idx=0; Idx *Packet = RelayQueue.Packet+Idx; if(Packet->Alloc==0) continue; if(Packet->Packet.Header.NonPos) continue; uint32_t Dist= IntDistance(Packet->LatDist, Packet->LonDist); // [m] uint32_t Dir = IntAtan2(Packet->LonDist, Packet->LatDist); // [16-bit cyclic] Dir &= 0xFFFF; Dir = (Dir*360)>>16; // [deg] uint8_t Len=0; Len+=Format_String(Line+Len, AcftTypeName[Packet->Packet.Position.AcftType]); Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, Packet->Packet.DecodeAltitude(), 4); // [m] altitude Line[Len++]='m'; Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, Dir, 3); // [deg] direction to target Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, (Dist+50)/100, 3, 1); // [km] distance to target Len+=Format_String(Line+Len, "km"); /* Line[Len++]='0'+Packet->Packet.Header.AddrType; Line[Len++]=':'; Len+=Format_Hex(Line+Len, Packet->Packet.Header.Address, 6); Line[Len++]=' '; Len+=Format_Hex(Line+Len, Packet->Rank); Line[Len++]=' '; Line[Len++]=':'; Len+=Format_UnsDec(Line+Len, Packet->Packet.Position.Time, 2); */ Line[Len]=0; u8g2_DrawStr(OLED, 0, (LineIdx+3)*8, Line); LineIdx++; if(LineIdx>=8) break; } } #ifdef WITH_LOOKOUT void OLED_DrawLookout(u8g2_t *OLED, GPS_Position *GPS) { u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); uint8_t Len=Format_String(Line, "=> "); if(Look.WarnLevel) { const LookOut_Target *Tgt = Look.Target+Look.WorstTgtIdx; Len+=Format_Hex(Line+Len, Tgt->ID, 7); Line[Len++]='/'; Line[Len++]='0'+Tgt->WarnLevel; Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, Tgt->TimeMargin>>1); Line[Len++]='s'; } Line[Len]=0; u8g2_DrawStr(OLED, 0, 24, Line); uint8_t LineIdx=1; for( uint8_t Idx=0; IdxAlloc) continue; uint8_t Len=0; Len+=Format_Hex(Line+Len, Tgt->ID, 7); Line[Len++]=' '; if(Tgt->DistMargin) { Len+=Format_UnsDec(Line+Len, Tgt->HorDist>>1); Line[Len++]='m'; } else { Len+=Format_UnsDec(Line+Len, Tgt->TimeMargin>>1); Line[Len++]='s'; Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, Tgt->MissDist>>1); Line[Len++]='m'; } Line[Len]=0; u8g2_DrawStr(OLED, 0, (LineIdx+3)*8, Line); LineIdx++; if(LineIdx>=8) break; } } void OLED_DrawTrafWarn(u8g2_t *OLED, GPS_Position *GPS) { u8g2_SetFont(OLED, u8g2_font_9x15_tr); if(Look.WarnLevel==0) return; const LookOut_Target *Tgt = Look.Target+Look.WorstTgtIdx; uint8_t Len=0; Len+=Format_Hex(Line+Len, Tgt->ID, 7); // ID of the target Line[Len++]='/'; Line[Len++]='0'+Tgt->WarnLevel; // warning level Line[Len]=0; u8g2_DrawStr(OLED, 0, 30, Line); Len=0; Len+=Format_UnsDec(Line+Len, Tgt->TimeMargin*5, 2, 1); // time-to-impact Line[Len++]='s'; Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, Tgt->MissDist*5, 2, 1); // miss-distance Line[Len++]='m'; Line[Len]=0; u8g2_DrawStr(OLED, 0, 45, Line); Len=0; Len+=Format_UnsDec(Line+Len, Tgt->getRelHorSpeed()*5, 2, 1); // horizontal speed Line[Len++]='m'; Line[Len++]='/'; Line[Len++]='s'; Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, Tgt->HorDist*5, 2, 1); // horizontal distance Line[Len++]='m'; Line[Len]=0; u8g2_DrawStr(OLED, 0, 60, Line); } #endif // WITH_LOOKOUT void OLED_DrawBaro(u8g2_t *OLED, GPS_Position *GPS) { bool isAltitudeUnitMeter = Parameters.AltitudeUnit == 0; // display altitude in meters bool isAltitudeUnitFeet = Parameters.AltitudeUnit == 1; // display altitude in feet bool isVarioUnitMPS = Parameters.VarioUnit == 0; // display Vario in m/s bool isVarioUnitFPM = Parameters.VarioUnit == 1; // display Vario in fpm u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line uint8_t Len=0; #ifdef WITH_BMP180 Len+=Format_String(Line+Len, "BMP180 "); #endif #ifdef WITH_BMP280 Len+=Format_String(Line+Len, "BMP280 "); #endif #ifdef WITH_BME280 Len+=Format_String(Line+Len, "BME280 "); #endif #ifdef WITH_MS5607 Len+=Format_String(Line+Len, "MS5607 "); #endif #ifdef WITH_MS5611 Len+=Format_String(Line+Len, "MS5611 "); #endif if(GPS && GPS->hasBaro) { Len+=Format_UnsDec(Line+Len, GPS->Pressure/4, 5, 2); Len+=Format_String(Line+Len, "hPa "); } else Len+=Format_String(Line+Len, "----.--hPa "); Line[Len]=0; u8g2_DrawStr(OLED, 0, 24, Line); Len=0; if(GPS && GPS->hasBaro) { if(isAltitudeUnitMeter) { Len+=Format_SignDec(Line+Len, GPS->StdAltitude, 5, 1); Len+=Format_String(Line+Len, "m "); } else if(isAltitudeUnitFeet) { Len+=Format_SignDec(Line+Len, (GPS->StdAltitude*336+512)>>10, 5, 0); Len+=Format_String(Line+Len, "ft "); } if(isVarioUnitMPS) { Len+=Format_SignDec(Line+Len, GPS->ClimbRate, 2, 1); Len+=Format_String(Line+Len, "m/s "); } else if(isVarioUnitFPM) { Len+=Format_SignDec(Line+Len, (GPS->ClimbRate*5039+128)>>8, 2, 0); Len+=Format_String(Line+Len, "fpm "); } } else { if(isAltitudeUnitMeter) Len+=Format_String(Line+Len, "-----.-m"); else if(isAltitudeUnitFeet) Len+=Format_String(Line+Len, "-----ft "); Len+=Format_String(Line+Len, " --.-m/s "); } Line[Len]=0; u8g2_DrawStr(OLED, 0, 36, Line); Len=0; if(GPS && GPS->hasBaro) { Len+=Format_SignDec(Line+Len, GPS->Temperature, 2, 1); Line[Len++]=0xB0; Line[Len++]='C'; Line[Len++]=' '; Len+=Format_SignDec(Line+Len, GPS->Humidity, 2, 1); Line[Len++]='%'; } else Len+=Format_String(Line+Len, "---.- C --.-% "); Line[Len]=0; u8g2_DrawStr(OLED, 0, 48, Line); } 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 int8_t Cap=MAVLINK_BattCap; // [%] from the drone's telemetry #else 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); // u8g2_SetFont(OLED, u8g2_font_6x10_tr); // u8g2_DrawStr(OLED, 0, 24, Line); // u8g2_DrawStr(OLED, 0, 24, "Battery"); u8g2_SetFont(OLED, u8g2_font_9x15_tr); 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 Format_UnsDec(Line, MAVLINK_BattVolt, 4, 3); #else Format_UnsDec(Line, BatteryVoltage>>8, 4, 3); // print the battery voltage readout #endif u8g2_DrawStr(OLED, 0, 48, Line); #ifdef WITH_MAVLINK strcpy(Line, " . A"); Format_UnsDec(Line, MAVLINK_BattCurr, 4, 2); #else strcpy(Line, " . mV/min "); // print the battery voltage rate Format_SignDec(Line, (600*BatteryVoltageRate+128)>>8, 3, 1); #endif u8g2_DrawStr(OLED, 0, 60, Line); #ifdef WITH_BQ uint8_t Fault = BQ.readFault(); // read fault register uint8_t ChargeErr = (Fault>>4)&0x3F; // charging fault: bool BattErr = Fault&0x08; // Battery OVP bool OTGerr = Fault&0x40; // VBus problem uint8_t NTCerr = Fault&0x03; // uint8_t Status = BQ.readStatus(); // read status register uint8_t State = (Status>>4)&0x03; // charging status const char *StateName[4] = { "" /* "Not charging" */ , "Pre-charge", "Charging", "Full" } ; // u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); u8g2_SetFont(OLED, u8g2_font_6x10_tr); if(Fault) { strcpy(Line, "Fault: "); Format_Hex(Line+7, Fault); Line[9]=0; u8g2_DrawStr(OLED, 60, 28, Line); } else u8g2_DrawStr(OLED, 60, 28, StateName[State]); // u8g2_DrawStr(OLED, 0, 60, Status&0x04?"Power-is-Good":"Power-is-not-Good"); /* print BQ registers for debug u8g2_SetFont(OLED, u8g2_font_6x10_tr); for(uint8_t Reg=0; Reg<=10; Reg++) { uint8_t Val = BQ.readReg(Reg); Format_Hex(Line+3*Reg, Val); Line[3*Reg+2]=' '; } Line[33]=0; u8g2_DrawStr(OLED, 0, 60, Line+15); Line[15]=0; u8g2_DrawStr(OLED, 0, 50, Line); */ #endif #ifdef WITH_AXP uint16_t InpCurr=AXP.readBatteryInpCurrent(); // [mA] uint16_t OutCurr=AXP.readBatteryOutCurrent(); // [mA] int16_t Current = InpCurr-OutCurr; // uint8_t Charging = Current>0; u8g2_SetFont(OLED, u8g2_font_6x10_tr); strcpy(Line, " mA "); Format_SignDec(Line, Current, 3); u8g2_DrawStr(OLED, 60, 28, Line); #endif } void OLED_DrawStatusBar(u8g2_t *OLED, GPS_Position *GPS) // status bar on top of the OLED { static bool Odd=0; #ifdef WITH_MAVLINK int8_t Cap = MAVLINK_BattCap; // [%] #else 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 ? #ifdef WITH_BQ uint8_t Status = BQ.readStatus(); Charging = (Status>>4)&0x03; #endif #ifdef WITH_AXP uint16_t InpCurr=AXP.readBatteryInpCurrent(); // [mA] uint16_t OutCurr=AXP.readBatteryOutCurrent(); // [mA] int16_t Current = InpCurr-OutCurr; Charging = Current>0; #endif #if defined(WITH_BQ) || defined(WITH_AXP) static uint8_t DispLev = 0; if(Charging==1 || Charging==2) { DispLev++; if(DispLev>5) DispLev = BattLev?BattLev-1:0; } else { DispLev = BattLev; } #else uint8_t &DispLev = BattLev; #endif 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); u8g2_DrawGlyph(OLED, 24, 12, 0x73); } #endif #ifdef WITH_BT_SPP if(BT_SPP_isConnected()) { u8g2_SetFont(OLED, u8g2_font_open_iconic_all_1x_t); u8g2_DrawGlyph(OLED, 36, 11, 0x5E); } // 0x4A #endif // #ifdef WITH_STRATUX // if(Stratux_isConnected()) // { u8g2_SetFont(OLED, u8g2_font_open_iconic_all_1x_t); // u8g2_DrawGlyph(OLED, 43, 11, 0x50); } // #endif #ifdef WITH_WIFI if(WIFI_isConnected()) { u8g2_SetFont(OLED, u8g2_font_open_iconic_all_1x_t); u8g2_DrawGlyph(OLED, 43, 11, 0x119); } // 0x50 #endif #ifdef WITH_AP if(WIFI_isAP()) { u8g2_SetFont(OLED, u8g2_font_open_iconic_all_1x_t); u8g2_DrawGlyph(OLED, 43, 11, 0xF8); } // 0x50 #endif // u8g2_SetFont(OLED, u8g2_font_5x7_tr); // u8g2_SetFont(OLED, u8g2_font_5x8_tr); static uint8_t Sec=0; u8g2_SetFont(OLED, u8g2_font_6x12_tr); // strcpy(Line, "[ %] --sat --:--"); strcpy(Line, "--sat --:--Z"); if(GPS && GPS->isTimeValid()) { Format_UnsDec (Line+6, (uint16_t)GPS->Hour, 2, 0); Line[8]=':'; Format_UnsDec (Line+9, (uint16_t)GPS->Min, 2, 0); } else Format_String(Line+6, "--:--"); if(GPS) { if(Sec) { Format_UnsDec(Line, (uint16_t)GPS->Satellites, 2); memcpy(Line+2, "sat", 3); } else { Format_UnsDec(Line, (uint16_t)(GPS_SatSNR+2)/4, 2); memcpy(Line+2, "dB ", 3);} } else Format_String(Line, "--sat"); u8g2_DrawStr(OLED, 52, 10, Line); Sec++; if(Sec>=3) Sec=0; } #ifdef WITH_AP void OLED_DrawNetwork(u8g2_t *OLED, GPS_Position *GPS) { // u8g2_SetFont(OLED, u8g2_font_6x12_tr); u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line uint8_t Len=Format_String(Line, "AP: "); Len+=Format_String(Line+Len, (const char *)WIFI_Config.ap.ssid, 0, WIFI_Config.ap.ssid_len); Line[Len]=0; u8g2_DrawStr(OLED, 0, 24, Line); Len=Format_String(Line, "IP: "); Len+=IP_Print(Line+Len, WIFI_IP.ip.addr); // Len+=Format_String(Line+Len, " CH"); // Len+=Format_UnsDec(Line+Len, WIFI_Config.ap.channel); Line[Len]=0; u8g2_DrawStr(OLED, 0, 36, Line); /* Len=Format_String(Line, "GW: "); Len+=IP_Print(Line+Len, WIFI_IP.gw.addr); Line[Len]=0; u8g2_DrawStr(OLED, 0, 48, Line); */ } #endif void OLED_DrawSystem(u8g2_t *OLED, GPS_Position *GPS) { // u8g2_SetFont(OLED, u8g2_font_6x12_tr); u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line uint8_t Len=0; #ifdef WITH_MAVLINK Len+=Format_String(Line+Len, "MAVLINK "); #else Len+=Format_String(Line+Len, "GPS "); #ifdef WITH_GPS_UBX Len+=Format_String(Line+Len, "UBX "); #endif #ifdef WITH_GPS_MTK Len+=Format_String(Line+Len, "MTK "); #endif #ifdef WITH_GPS_SRF Len+=Format_String(Line+Len, "SRF "); #endif #endif // WITH_MAVLINK Len+=Format_UnsDec(Line+Len, GPS_getBaudRate(), 1); Len+=Format_String(Line+Len, "bps"); Line[Len]=0; u8g2_DrawStr(OLED, 0, 24, Line); Len=0; #ifdef WITH_RFM69 Len+=Format_String(Line+Len, "RFM69 v"); // Type of RF chip used if(Parameters.RFchipTypeHW) Line[Len++]='H'; Line[Len++]='W'; #endif #ifdef WITH_RFM95 Len+=Format_String(Line+Len, "RFM95 v"); #endif #ifdef WITH_SX1262 Len+=Format_String(Line+Len, "SX1262 v"); #endif #ifdef WITH_SX1272 Len+=Format_String(Line+Len, "SX1272 v"); #endif Len+=Format_Hex(Line+Len, TRX.chipVer); Line[Len++]=' '; Len+=Format_SignDec(Line+Len, (int16_t)TRX.chipTemp); Line[Len++]=0xB0; Line[Len++]='C'; Line[Len]=0; u8g2_DrawStr(OLED, 0, 36, Line); Len=0; #ifdef WITH_BMP180 Len+=Format_String(Line+Len, "BMP180 0x"); Len+=Format_Hex(Line+Len, Baro.ADDR); #endif #ifdef WITH_BMP280 Len+=Format_String(Line+Len, "BMP280 0x"); Len+=Format_Hex(Line+Len, Baro.ADDR); #endif #ifdef WITH_BME280 Len+=Format_String(Line+Len, "BME280 0x"); Len+=Format_Hex(Line+Len, Baro.ADDR); #endif #ifdef WITH_MS5607 Len+=Format_String(Line+Len, "MS5607 0x"); Len+=Format_Hex(Line+Len, Baro.ADDR); #endif #ifdef WITH_MS5611 Len+=Format_String(Line+Len, "MS5611 0x"); Len+=Format_Hex(Line+Len, Baro.ADDR); #endif Line[Len]=0; u8g2_DrawStr(OLED, 0, 48, Line); #ifdef WITH_SPIFFS Len=0; Len+=Format_String(Line+Len, "SPIFFS "); size_t Total, Used; if(SPIFFS_Info(Total, Used)==0) // get the SPIFFS usage summary { Len+=Format_UnsDec(Line+Len, (Total-Used)/1024); Len+=Format_String(Line+Len, "/"); Len+=Format_UnsDec(Line+Len, Total/1024); Len+=Format_String(Line+Len, "kB"); } Line[Len]=0; u8g2_DrawStr(OLED, 0, 60, Line); #endif /* #ifdef WITH_SD Len=0; Len+=Format_String(Line+Len, "SD "); if(SD_isMounted()) { Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectors()); Line[Len++]='x'; Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectorSize()*5/512, 2, 1); Len+=Format_String(Line+Len, "KB"); } else { Len+=Format_String(Line+Len, "none"); } Line[Len]=0; u8g2_DrawStr(OLED, 0, 60, Line); #endif */ } void OLED_DrawID(u8g2_t *OLED, GPS_Position *GPS) { u8g2_SetFont(OLED, u8g2_font_9x15_tr); Parameters.Print(Line); Line[10]=0; u8g2_DrawStr(OLED, 26, 25, Line); // u8g2_SetFont(OLED, u8g2_font_10x20_tr); u8g2_SetFont(OLED, u8g2_font_7x13_tf); u8g2_DrawStr(OLED, 0, 24, "ID:"); if(Parameters.Pilot[0] || Parameters.Reg[0]) { strcpy(Line, "Pilot: "); strcat(Line, Parameters.Pilot); u8g2_DrawStr(OLED, 0, 37, Line); strcpy(Line, "Reg: "); strcat(Line, Parameters.Reg); u8g2_DrawStr(OLED, 0, 49, Line); } else { #ifdef WITH_FollowMe u8g2_DrawStr(OLED, 15, 37, "FollowMe868"); u8g2_DrawStr(OLED, 20, 49, "by AVIONIX"); #endif } // u8g2_SetFont(OLED, u8g2_font_5x8_tr); u8g2_SetFont(OLED, u8g2_font_6x12_tr); // u8g2_DrawStr(OLED, 96, 62, "v" STR(VERSION)); uint64_t ID=getUniqueID(); uint8_t Len=Format_String(Line, "#"); Len+=Format_Hex(Line+Len, (uint16_t)(ID>>32)); Len+=Format_Hex(Line+Len, (uint32_t)ID); // Line[Len++]=' '; // Line[Len++]='v'; Len+=Format_String(Line+Len, " v"STR(VERSION)); Line[Len]=0; u8g2_DrawStr(OLED, 0, 62, Line); } void OLED_DrawAltitudeAndSpeed(u8g2_t *OLED, GPS_Position *GPS) { uint8_t Len; bool isAltitudeUnitMeter = Parameters.AltitudeUnit == 0; // display altitude in meters bool isAltitudeUnitFeet = Parameters.AltitudeUnit == 1; // display altitude in feet bool isSpeedUnitKMH = Parameters.SpeedUnit == 0; // display Speed in km/h bool isSpeedUnitKnot = Parameters.SpeedUnit == 1; // display Speed in knot bool isVarioUnitMPS = Parameters.VarioUnit == 0; // display Vario in m/s bool isVarioUnitFPM = Parameters.VarioUnit == 1; // display Vario in fpm // Standard Pressure Altitude if(GPS && (GPS->hasBaro || GPS->isValid())) // if GPS has lock or just the pressure data { int32_t Alt = GPS->Altitude; // [0.1m/s] take GPS (geometrical) altitude if(GPS->hasBaro) Alt = GPS->StdAltitude; // but if pressure sensor is there then replace with pressure altitude if(isAltitudeUnitMeter) Alt = (Alt+5)/10; // [0.1m] => [m] // or to meters else if(isAltitudeUnitFeet) Alt = (Alt*336+512)>>10; // [0.1m] => [feet] // convert to feet Len=Format_SignDec(Line, Alt, 1, 0, 1); } // print altitude into the string else Len=Format_String(Line, "----"); // if altitude not available then print place holders Line[Len]=0; u8g2_SetFont(OLED, u8g2_font_fub20_tr); // relatively big font uint8_t Altitude_width = u8g2_GetStrWidth(OLED, Line); // how wide the string would be on the OLED u8g2_DrawStr(OLED, 60-Altitude_width, 40, Line); // print the string u8g2_SetFont(OLED, u8g2_font_9x15_tr); // smaller font if(isAltitudeUnitMeter) u8g2_DrawStr(OLED, 64, 40, "m"); else if(isAltitudeUnitFeet) u8g2_DrawStr(OLED, 62, 40, "ft"); // print units u8g2_SetFont(OLED, u8g2_font_fub17_tr); if(GPS && GPS->isValid()) { uint16_t Heading = (GPS->Heading+5)/10; if(Heading>=360) Heading-=360; Len=Format_UnsDec(Line, Heading, 3); } else Len=Format_String(Line, "---"); Line[Len]=0; uint8_t Track_width = u8g2_GetStrWidth(OLED, Line); u8g2_DrawStr(OLED, 118-Track_width, 40, Line); u8g2_SetFont(OLED, u8g2_font_6x12_tr); // small font u8g2_DrawStr(OLED, 122, 28, "o"); // degree sign if(GPS && (GPS->hasBaro || GPS->isValid())) // if GPS has lock or just the pressure data { int16_t vario_value = GPS->ClimbRate; // [0.1m/s] climb rate if(isVarioUnitFPM) vario_value = (vario_value*5039+128)>>8; // [0.1m/s] => [feet per meter] if(vario_value<0) { vario_value=(-vario_value); const int minus_width=10; const int minus_height=17; static unsigned char minus_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; u8g2_DrawXBM(OLED, 0, 47, minus_width, minus_height, minus_bits); } else { const int plus_width=10; const int plus_height=17; static unsigned char plus_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; u8g2_DrawXBM(OLED, 0, 47, plus_width, plus_height, plus_bits); } if(isVarioUnitMPS) Len=Format_UnsDec(Line, vario_value, 2, 1); if(isVarioUnitFPM) Len=Format_UnsDec(Line, vario_value, 2, 0); } else Len=Format_String(Line, "-.-"); Line[Len]=0; u8g2_SetFont(OLED, u8g2_font_fub17_tr); uint8_t Vario_width = u8g2_GetStrWidth(OLED, Line); u8g2_DrawStr(OLED, 54-Vario_width, 64, Line); if(isVarioUnitMPS) { const int ms_width=7; const int ms_height=17; static unsigned char ms_bits[] = { 0x00, 0x00, 0x16, 0x2a, 0x2a, 0x2a, 0x2a, 0x00, 0x7f, 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x22, 0x1c }; u8g2_DrawXBM(OLED, 58, 47, ms_width, ms_height, ms_bits); } else if(isVarioUnitFPM) { const int ms_width=7; const int ms_height=17; static unsigned char ms_bits[] = { 0x84, 0xa2, 0xf7, 0xa2, 0xa2, 0xa2, 0xc2, 0x80, 0xff, 0x80, 0x00, 0x16, 0x2a, 0x2a, 0x2a, 0x2a, 0x00 }; u8g2_DrawXBM(OLED, 58, 47, ms_width, ms_height, ms_bits); } // Speed if(GPS && GPS->isValid()) { uint16_t speed = (GPS->Speed*9+12)/25; if(isSpeedUnitKnot) speed = (GPS->Speed*199+512)>>10; Len=Format_UnsDec(Line, speed, 1, 0); } else Len=Format_String(Line, "--"); Line[Len]=0; u8g2_SetFont(OLED, u8g2_font_fub17_tr); uint8_t Speed_width = u8g2_GetStrWidth(OLED, Line); u8g2_DrawStr(OLED, 114-Speed_width, 64, Line); if(isSpeedUnitKMH) { const int kmh_width=10; const int kmh_height=17; static unsigned char kmh_bits[] = { 0x01, 0x00, 0x01, 0x00, 0x69, 0x01, 0xa5, 0x02, 0xa3, 0x02, 0xa5, 0x02, 0xa9, 0x02, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x38, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00 }; u8g2_DrawXBM(OLED, 118, 47, kmh_width, kmh_height, kmh_bits); } else if(isSpeedUnitKnot) { const int kmh_width=10; const int kmh_height=17; static unsigned char kmh_bits[] = { 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x01, 0xfc, 0x01, 0xfc, 0xe9, 0xfc, 0x25, 0xfd, 0x23, 0xfd, 0x25, 0xfd, 0x29, 0xfd, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc}; u8g2_DrawXBM(OLED, 118, 47, kmh_width, kmh_height, kmh_bits); } } void OLED_DrawFlight(u8g2_t *OLED, GPS_Position *GPS) // draw flight status page { u8g2_SetFont(OLED, u8g2_font_7x13_tf); u8g2_DrawStr(OLED, 0, 28, "Flight"); } void OLED_DrawLoRaWAN(u8g2_t *OLED, GPS_Position *GPS) // draw LoRaWAN status page { u8g2_SetFont(OLED, u8g2_font_7x13_tf); #ifndef WITH_LORAWAN u8g2_DrawStr(OLED, 0, 28, "LoRaWAN: -OFF-"); #endif #ifdef WITH_LORAWAN const char *StateName[4] = { "Not-Joined", "Join-Req", "+Joined+", "PktSend" } ; int Len=Format_String(Line, "LoRaWAN: "); if(WANdev.State==2) Len+=Format_Hex(Line+Len, WANdev.DevAddr); else if(WANdev.State<=3) Len+=Format_String(Line+Len, StateName[WANdev.State]); else Len+=Format_Hex(Line+Len, WANdev.State); Line[Len]=0; u8g2_DrawStr(OLED, 0, 24, Line); // if(WANdev.State<2) // { Len =Format_HexBytes(Line, WANdev.AppKey , 8); Line[Len]=0; u8g2_DrawStr(OLED, 12, 36, Line); // Len =Format_HexBytes(Line, WANdev.AppKey+8, 8); Line[Len]=0; u8g2_DrawStr(OLED, 12, 48, Line); // u8g2_SetFont(OLED, u8g2_font_open_iconic_all_1x_t); u8g2_DrawGlyph(OLED, 1, 36, 0xC1); u8g2_DrawGlyph(OLED, 1, 48, 0xC1); } // else if(WANdev.State>=2) { Len =Format_String(Line , "Up: "); Len+=Format_Hex(Line+Len, (uint16_t)WANdev.UpCount); Len+=Format_String(Line+Len, " Dn: "); Len+=Format_Hex(Line+Len, (uint16_t)WANdev.DnCount); Line[Len]=0; u8g2_DrawStr(OLED, 0, 36, Line); Len =Format_String(Line , "Rx:"); Len+=Format_SignDec(Line+Len, (int16_t)WANdev.RxRSSI, 3); Len+=Format_String(Line+Len, "dBm "); Len+=Format_SignDec(Line+Len, ((int16_t)WANdev.RxSNR*10+2)>>2, 2, 1); Len+=Format_String(Line+Len, "dB"); Line[Len]=0; u8g2_DrawStr(OLED, 0, 48, Line); } Len=Format_HexBytes(Line, WANdev.AppKey, 2); Line[Len++]='.'; Line[Len++]='.'; Len+=Format_Hex(Line+Len, WANdev.AppKey[15]); Line[Len]=0; u8g2_DrawStr(OLED, 72, 60, Line); u8g2_SetFont(OLED, u8g2_font_open_iconic_all_1x_t); u8g2_DrawGlyph(OLED, 61, 60, 0xC1); // if(WANdev.State>=2) { } /* Len=0; for(int Idx=0; Idx<16; Idx++) { Len+=Format_Hex(Line+Len, WANdev.AppKey[Idx]); } Line[Len]=0; u8g2_SetFont(OLED, u8g2_font_5x8_tr); u8g2_DrawStr(OLED, 0, 48, Line); */ #endif // WITH_LORAWAN } #endif // ========================================================================================================================