Marco Zonca
Published © LGPL

Mountain remote Weather Station (Wunderground & APRS)

Place a Weather Station remotely on a mountain, radio transmitter and receiver using two LilyGO LoRa32 SX1262 equipment, network data relay.

AdvancedFull instructions provided861
Mountain remote Weather Station (Wunderground & APRS)

Things used in this project

Hardware components

LILYGO® TTGO LoRa32 T3 V1.6.1
LILYGO® TTGO LoRa32 T3 V1.6.1
×2

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

3D printing Enclosure box

3D printing Back cover

3D printing front cover

Schematics

Schematic diagram

Code

Arduino code, RECEIVER (home side)

Arduino
/*
   ard-weather868receiver, by Marco Zonca 12/2025, version 1.0
   
   - The sketch receives Fanet like, type 4 service, weather data from a remote station and forward them via WiFi
     to aprs.fi and/or wundermap.com; it updates his time via NTP; is active the watchdog;
     autocorrecting receiving frequency, packet after packet; it is made for LilyGO LoRa32 version 1.6.1
     
   IMPORTANT:
   ----------
   String IDtoReceive = "XXYYZZ";  // remote weather station ID to receive; this sketch will receive only
                                   // this one so see serial monitor log at Transmitter side to obtain it

      Mods:
      14.01.25   - APRS_windgustmph was=wu_windgustmph, now=wu_windgustmph_10m
                 - added failures_LEN to count packet len non accepted if correct ID received, telemetry too
                 - added in telemetry abs(RSSI) for the last received packet
      15.01.2026 - modified schedule time to send to Wunder (5', was 7') and APRS (9', was 11')
  
 */

#define RADIOLIB_GODMODE 1  // Must be BEFORE the include, needed to modify registers of RadioLib

#include <RadioLib.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/TomThumb.h>
#include <WiFi.h>
#include <NTPClient.h>
#include "TimeLib.h"
#include <esp_task_wdt.h>
#include <Preferences.h>
#include <rom/rtc.h>

#define OLED_RESET -1
#define SCREEN_WIDTH 128    // OLED display width, in pixels
#define SCREEN_HEIGHT 32    // OLED display height, in pixels
#define SCREEN_ADDRESS 0x3C // See datasheet for Address, 0x3C for 128x32
#define FRQ_CORRECTION 0.013500
#define BATTERY_PIN 35
#define WDT_TIMEOUT 60000  // watchdog 60", in millis

const boolean isDebug = true;

//*************************//personalize down here//********************************
String IDtoReceive = "AABBCC";  // remote weather station ID to receive; this sketch will receive only
                                // this one so see serial monitor log at Transmitter side to obtain it
double txrxFrequency = 868.700000;  // working frq in Mhz
const unsigned long OLED_time_off = (10 * 60 * 1000); // 10' minutes to switch off display

//************ WIFI, NTP
const char WiFi_SSID[40] = "YOURSSID";  // put your SSID WiFi access point here
const char WiFi_PASS[40] = "YOURPASS";  // put your PASSWORD WiFi access point here
const char poolUrl[] = "it.pool.ntp.org";  // NTP url server
const byte maxCONN_attempts = 8;  // nr. of WiFi connection attempts
const byte maxTIME_attempts = 4;  // nr. of NTP update attempts
const unsigned long updateNTPRate = (180 * (60 * 1000)); // 180 minutes for NTP update
const long utcOffset = +3600;  // timezone for local time in seconds (+3600" = +1h)

//************ APRS
const boolean isAPRSenabled = false;
const unsigned long APRSUpdateRate = (11 * ((60) * 1000));  // 11 minutes for APRS update
const char myAPRScallsign[40] = "ABCDEF-13"; // your ham radio callsign, user
const char myAPRSpasscode[40] = "012345"; // your ham radio passcode, pass
const char APRSserver[40] = "rotate.aprs2.net";  // APRS url server
const long APRSport = 14580;  // APRS server port
const char myAPRSlatLon[40] = "4025.14N/01433.12E"; // remote weather Station Position DDMM.MMy/DDDMM.MMx

//************ WUNDERGROUND
const boolean isWUNDERenabled = false;
const char mywunderID[40] = "ILELEL3";  // your weather station ID
const char mywunderPASS[40] = "8u8u8u";  // your weather station password
const unsigned long wunderUpdateRate = (7 * ((60) * 1000));  // 7 minutes for Wundeground update

//*************************//personalize up here//********************************

byte mustLen = 17;  // rx packet len
String IDRX="";
String TV[6]={"","","","","",""};  // 0-4 = actual display, 5 = next line to show
String telemetry_in_comment="";
String netResponse="";
String trimmed="";
const byte ledPin=25;
float totFRQerr=0;
boolean isFirstRX=true;
boolean isTimeUpdated=false;
boolean isOLEDoff=false;
unsigned long prevNTPMillis = 0;
unsigned long actualNTPMillis = 0;
unsigned long actualAPRSMillis = 0;
unsigned long prevAPRSMillis = 0;
unsigned long prevWundMillis = 0;
unsigned long actualWundMillis = 0;
unsigned long prevOLEDMillis = 0;
unsigned long actualOLEDMillis = 0;
double min2upd=0;
uint8_t str[256];
int state = 0;
int len = 0;
int type = 0;
int windspeedscaling = 0;
int windgustscaling = 0;
double dpNC=0;
double dewptC=0;
double dpA=0;
double dpB=0;
double dpC=0;
double wind10gustmph[11]={0,0,0,0,0,0,0,0,0,0,0};
byte w10g=0;

float rssi = 0;
float snr = 0;
float freqError = 0;
float v = 0;
float rv = 0;
long failures_NTP = 0;
long failures_WUNDER = 0;
long failures_WIFI = 0;
long failures_APRS = 0;
long failures_LEN = 0;
boolean isDailyResetted = false;
boolean isTooFailures = false;
unsigned int reboot_count=0;
int netRsize=640;
byte btemp=0;
int itemp=0;
float ftemp=0;
char ch=0;
byte pointer=0;
byte cHH=0;
byte cMM=0;
byte sensorBatteryStatus=0;

long APRS_day=0;
long APRS_hour=0;
long APRS_minute=0;
String APRSPacket="";
long APRS_winddir=0;
long APRS_windspeedmph=0;
long APRS_windgustmph=0;
long APRS_tempfpos=0;
boolean isAPRS_tempf_negative=false;
long APRS_rain100in=0;
long APRS_raindaily100in=0;
long APRS_humidity=0;
long APRS_barom10hpa=0;
long APRS_solarradiationwm2=0;
long APRS_reboots=0;
long APRS_battery10=0;
String APRSlogin = "user " + String(myAPRScallsign) + " pass " + String(myAPRSpasscode) + " vers ArduinoWX 1.0\n";

boolean remote_wind_ok = false;
boolean remote_tempf_ok = false;
boolean remote_rainin_ok = false;
boolean remote_humidity_ok = false;
boolean remote_baromin_ok = false;
boolean remote_solarradiationwm2_ok = false;
boolean remote_reboots_ok = false;
boolean remote_battery_ok = false;
boolean remote_dewptf_ok=false;

double wu_tempf=0;
double wu_temp_c=0;
double wu_humidity=0;
double wu_windspeedmph=0;
double wu_windgustmph=0;
double wu_winddir=0;
double wu_solarradiationwm2=0;
double wu_rainin=0;
double wu_dailyrainin=0;
double wu_UV=0;
double wu_baromin=0;
double wu_indoortempf=0;
double wu_indoorhumidity=0;
double wu_dewptf=0;
double wu_windspdmph_avg2m=0;
double wu_winddir_avg2m=0;
double wu_windgustmph_10m=0;
String wuPacket="";
double wu_windspeedkmh=0;
double wu_windgustkmh=0;

char wuserver[]="weatherstation.wunderground.com";  // wunderground url

time_t epoch = 0;
time_t epochUTC = 0;
WiFiClient client;

// Pins for LilyGo LoRa32 v1.6.1 (Original ESP32 + SX1276)
// NSS, DIO0, RST, DIO1
SX1276 radio = new Module(18, 26, 23, 33); 
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, poolUrl, utcOffset);
Preferences preferences;

// Interrupt flag
volatile bool receivedFlag = false;

// Interrupt Service Routine (ISR)
#if defined(ESP8266) || defined(ESP32)
  ICACHE_RAM_ATTR
#endif

void setup() {
  if (isDebug) {Serial.begin(115200);}
  delay(2000);
  if (isDebug) {Serial.println(F(""));}
  if (isDebug) {Serial.println(F(""));}
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  TV[0].reserve(60);
  TV[1].reserve(60);
  TV[2].reserve(60);
  TV[3].reserve(60);
  TV[4].reserve(60);
  TV[5].reserve(60);
  IDRX.reserve(7);
  IDtoReceive.reserve(7);
  APRSPacket.reserve(640);
  wuPacket.reserve(640);
  telemetry_in_comment.reserve(128);
  netResponse.reserve(netRsize);
  APRSlogin.reserve(64);
  trimmed.reserve(6);
  
  Wire.begin(OLED_SDA, OLED_SCL);
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    if (isDebug) {Serial.println(F("OLED allocation failed"));}
   }else{
    if (isDebug) {Serial.println(F("OLED display ok"));}
  }

  // Initialize for Remote WX: freq868.7MHz, bw250kHz, sf7, cr8, syncWord 0xF1, txpower 10 (-4 to 20dbm), Preamble 8, rxgain 0=AGC (0-6, 6=min gain)
  // Initialize for FANET: freq868.2MHz, bw250kHz, sf7, cr8, syncWord 0xF1, txpower 10 (-4 to 20dbm), Preamble 8, rxgain 0=AGC (0-6, 6=min gain)
  // frequency Mhz, bandwidth, spreading factor 6-12, coding rate 4/x (8), syncword, power, preamblelength, gain
  if (isDebug) {Serial.print(F("RadioLib initializing... "));}
  state = radio.begin((txrxFrequency - FRQ_CORRECTION), 250.0, 7, 8, 0xF1, 10, 8, 0);
  if (state == RADIOLIB_ERR_NONE) {
    if (isDebug) {Serial.println(F("success!"));}
   } else {
    if (isDebug) {Serial.print(F("failed, error code="));}
    if (isDebug) {Serial.println(state);}
    while (true);
  }
  radio.explicitHeader(); // should be already as default value
  radio.setCRC(true); // should be already as default value

  // Critical: Apply GXAirCom's register settings for FANET ---------------
  radio.getMod()->SPIwriteRegister(0x31,0x43);  // was 0xC3
  uint8_t rawByte = radio.getMod()->SPIreadRegister(0x31);
  if (isDebug) {Serial.print(F("Register 0x31 content should be=0x43, now is: "));}
  if (isDebug) {Serial.print(F("0x"));}
  if ((rawByte <16) && (isDebug)) {Serial.print(F("0"));}
  if (isDebug) {Serial.println(rawByte,HEX);}
  rawByte = radio.getMod()->SPIreadRegister(0x37);
  if (isDebug) {Serial.print(F("Register 0x37 content should be=0x0A, now is: "));}
  if (isDebug) {Serial.print(F("0x"));}
  if ((rawByte <16) && (isDebug)) {Serial.print(F("0"));}
  if (isDebug) {Serial.println(rawByte,HEX);}
  rawByte = radio.getMod()->SPIreadRegister(0x26);
  if (isDebug) {Serial.print(F("Register 0x26 content should be=0x04, now is: "));}
  if (isDebug) {Serial.print(F("0x"));}
  if ((rawByte <16) && (isDebug)) {Serial.print(F("0"));}
  if (isDebug) {Serial.println(rawByte,HEX);}
  // Critical: Apply GXAirCom's register settings for FANET ---------------
  
  // --- FIX: USE THE NEW RADIOLIB 7.x FUNCTION ---
  radio.setPacketReceivedAction(setFlag);
  
  // Configure ADC attenuation for 0-3.3V range for pin 35, battery
  analogSetPinAttenuation(35, ADC_11db);  
  v = readBatteryVoltage();
  if (isDebug) {
    Serial.print(F("Battery voltage: "));
    Serial.println(v,2);
    Serial.println(F(""));
  }

  // watchdog activation
  esp_task_wdt_config_t twdt_config = {
      .timeout_ms = WDT_TIMEOUT,
      .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,    // Bitmask of all cores
      .trigger_panic = true,
  };
  esp_task_wdt_deinit(); // wdt is enabled by default, so we need to deinit it first
  esp_task_wdt_init(&twdt_config); // panic enabled so ESP32 restarts
  esp_task_wdt_add(NULL); // add current thread to WDT watch
  if (isDebug) {Serial.println(F("* Watchdog active *"));}

  // reboot counter due to watchdog or unusual way
  preferences.begin("reboot", false);  // reboot counter and why, false means r/w
    reboot_count = preferences.getUInt("count", 0);
    RESET_REASON reason = rtc_get_reset_reason(0); // Core 0
    if (reason != POWERON_RESET) {
      if (isDebug) {Serial.println(F("* ALERT: Last reset caused by watchdog or unusual reason! *"));}
      switch (reason) {
        case 1:  if (isDebug) {Serial.println(F("POWERON_RESET, 1, Vbat power on reset")); break;}
        case 3:  if (isDebug) {Serial.println(F("SW_RESET, 3, Software reset")); break;}
        case 4:  if (isDebug) {Serial.println(F("OWDT_RESET, 4, Legacy watch dog reset")); break;}
        case 5:  if (isDebug) {Serial.println(F("DEEPSLEEP_RESET, 5, Deep Sleep reset")); break;}
        case 6:  if (isDebug) {Serial.println(F("SDIO_RESET, 6, Reset by SLC module")); break;}
        case 7:  if (isDebug) {Serial.println(F("TG0WDT_SYS_RESET, 7, Timer Group0 Watch dog reset")); break;}
        case 8:  if (isDebug) {Serial.println(F("TG1WDT_SYS_RESET, 8, Timer Group1 Watch dog reset")); break;}
        case 9:  if (isDebug) {Serial.println(F("RTCWDT_SYS_RESET, 9, RTC Watch dog reset")); break;}
        case 10: if (isDebug) {Serial.println(F("INTRUSION_RESET, 10, Instrusion tested to reset CPU")); break;}
        case 11: if (isDebug) {Serial.println(F("TGWDT_CPU_RESET, 11, Time Group reset CPU")); break;}
        case 12: if (isDebug) {Serial.println(F("SW_CPU_RESET, 12, Software reset CPU")); break;}
        case 13: if (isDebug) {Serial.println(F("RTCWDT_CPU_RESET, 13, RTC Watch dog Reset CPU")); break;}
        case 14: if (isDebug) {Serial.println(F("EXT_CPU_RESET, 14, for APP CPU, reset by PRO CPU")); break;}
        case 15: if (isDebug) {Serial.println(F("RTCWDT_BROWN_OUT_RESET, 15, Reset when the vdd voltage is not stable")); break;}
        case 16: if (isDebug) {Serial.println(F("RTCWDT_RTC_RESET, 16, RTC Watch dog reset digital core and rtc module")); break;}
        default: if (isDebug) {Serial.println(F("NO_MEAN, unknown reason")); break;}
      }
      reboot_count++;
      if (reboot_count >9) {
        if (isDebug) {Serial.print(F("** Many reboots, needed to set the counter to zero! **"));}
        reboot_count=0;
      }
    preferences.putUInt("count", reboot_count);
    }    
  preferences.end();
  if (isDebug) {Serial.print(F("Actual watchdog/unusual reboot counter = "));}
  if (isDebug) {Serial.println(reboot_count);}

  // Show initial display
  TV[0]=" 868Mhz Weather data Receiver  ";
  TV[1]="  Send to Wunderground & APRS  ";
  TV[2]="    Battery = ";
  TV[2] += String(v,1);
  TV[2] +="  Resets=";
  TV[2] += String(reboot_count);
  TV[3]="       waiting for: ";
  TV[3] += IDtoReceive;
  TV[4]="              <<<<<<<<         ";
  showDisplay();
  delay(5000);

  // Read initial time
  setTime(967680000);  // initial datetime: 31/08/2000 00:00:00 in epoch format
  if (strcmp(WiFi_SSID,"") != 0) {  // update NTP time
    connectWiFi();
    TimeNTPUpdate();
    WiFidisconnect();
   } else {
    if (isDebug) {Serial.println(F("Empty SSID, no WiFi, noTimeSet!"));}
  }
  if (isTimeUpdated==false) {
    display.clearDisplay();
    display.setCursor(0,6);
    display.println(F("   Not possible to   "));
    display.println(F("  get initial Time   "));
    display.println(F("      via WiFi!!     "));
    display.println(F("    ** STOPPED **    "));
    display.display();
    while(true) {  // stop running, not possible to continue without actual time, MCU will restart by watchdog
      display.invertDisplay(true);
      delay(1000);
      display.invertDisplay(false);
      delay(1000);  
    }
  }
  if (isDebug) {printTime();}
  // Start initial listening
  radio.startReceive();
}//setup()


void loop() {
  if (((hour() == 0) && (minute() == 0)) && (isDailyResetted == false)){  // daily reset at 00:00 l.t.
    failures_NTP = 0;
    failures_WUNDER = 0;
    failures_WIFI = 0;
    failures_APRS = 0;
    failures_LEN = 0;
    if (reboot_count != 0) {
      preferences.begin("reboot", false);
       reboot_count = 0;
       preferences.putUInt("count", reboot_count);
      preferences.end();
    }            
    isDailyResetted=true;
  }
  if (((hour() == 0) && (minute() != 0)) && (isDailyResetted == true)){  // prepare for next reset
    isDailyResetted=false;
  }
  //--------------- time to switch off OLED
  actualOLEDMillis=millis();
  if (prevOLEDMillis > actualOLEDMillis) {  // manage millis() rollover
    prevOLEDMillis=actualOLEDMillis;
    actualOLEDMillis=prevOLEDMillis + OLED_time_off;
  }
  if (((prevOLEDMillis + OLED_time_off) <= actualOLEDMillis) && (isOLEDoff==false)) {
    display.ssd1306_command(SSD1306_DISPLAYOFF);
    isOLEDoff=true;
    prevOLEDMillis=millis();
  }
  if (receivedFlag == true) {  //---------------------------------------------- RX
      receivedFlag = false;    
      state = radio.readData(str, 255);
      len=radio.getPacketLength();
      rssi = radio.getRSSI();
      snr = radio.getSNR();
      freqError = radio.getFrequencyError();
      delay(10);
      if (state == RADIOLIB_ERR_NONE) {
          digitalWrite(ledPin, HIGH);
          if (isDebug) {printTime();}
          if (isFirstRX==true) {
            TV[0]="";
            TV[1]="";
            TV[2]="";
            TV[3]="";
            TV[4]="";
            showDisplay();
            isFirstRX=false;
          }
          cHH=hour();
          cMM=minute();
          type=(str[0] & 0x3F); // Extract FANET Type
          if (isDebug) {Serial.print(F("RX Type: "));}
          if (isDebug) {Serial.print(type);}
          if (isDebug) {Serial.print(F(" -> "));}
    
          // Print raw bytes for verification
          for (int i = 0; i < len; i++) {
            if ((str[i] < 16) && (isDebug)) {Serial.print(F("0"));}
            if (isDebug) {Serial.print(str[i], HEX);}
          }
          if (isDebug) {
            Serial.print(F(" len="));
            Serial.print(len,1);
            Serial.print(F(" snr="));
            Serial.print(snr,1);
            Serial.print(F(" rssi="));
            Serial.print(rssi,0);
            Serial.print(F(" frqErr="));
            Serial.print(freqError,0);
          }
    
          // prepare TV row for scrolling display 
          TV[5]="";
          if (cHH < 10) {TV[5] += "0";}  //       HH:MM
          TV[5] += String(cHH);
          TV[5] += ":";
          if (cMM < 10) {TV[5] += "0";}
          TV[5] += String(cMM);
          TV[5] += "  ";
          TV[5] += String(type);  //              type
          IDRX="";  //                            ID
          if (str[1] < 16) {IDRX += "0";}
          IDRX += String(str[1],HEX);
          if (str[3] < 16) {IDRX += "0";}
          IDRX += String(str[3],HEX);
          if (str[2] < 16) {IDRX += "0";}
          IDRX += String(str[2],HEX);
          IDRX.toUpperCase();
          if ((IDRX == IDtoReceive) && (type == 4)) {
            TV[5] += "   ";
           }else{
            TV[5] += " - ";
          }
          if (isDebug) {Serial.print(F(" IDRX="));}
          if (isDebug) {Serial.print(IDRX);}
          if (isDebug) {Serial.print(F(" IDtoReceive="));}
          if (isDebug) {Serial.println(IDtoReceive);}
          TV[5] += IDRX;
          TV[5] += "  ";
          TV[5] += String(len);  //                len
          TV[5] += "  ";
          TV[5] += String(rssi,0);  //             rssi
          TV[5] += "  ";
          TV[5] += String((freqError/1000),1);  // freqError in Khz
          v = readBatteryVoltage();
          TV[5] += "  ";
          TV[5] += String(v,1);  //                battery

          scrollDisplay();
          //------------------------------------------------------------------------- filter + decoding Fanet
          if ((IDRX == IDtoReceive) && (type == 4) && (len == mustLen)) {  // pass only the remote station specified, type 4 and len=mustLen
              if (isDebug) {Serial.println(F("ID filter OK, type=OK, len OK"));}
              // Correct radio frequency drift, every passed packet rx
              totFRQerr += freqError;
              radio.setFrequency(txrxFrequency - (FRQ_CORRECTION + (totFRQerr/1000000.0)));

              epochUTC = (now() - utcOffset);
              APRS_day=day(epochUTC);
              APRS_hour=hour(epochUTC);
              APRS_minute=minute(epochUTC);
              pointer=11;  // byte number to decode data (depending of bit set)

              if (isDebug) {Serial.print(F("APRS data:"));}

              remote_tempf_ok = false;  // bit 6, temperature, +1byte in 0.5 C, Complement 2 (normal))
              if (bitRead(str[4],6) == 1){
                remote_tempf_ok = true;
                btemp=str[pointer];
                pointer++;
                ftemp=(int8_t)(btemp);
                ftemp=(ftemp * 5.0 / 10.0); // units are in 0.5C, es. value 11 -> 11*5/10 = 5.5C
                wu_temp_c=Round1Dec(ftemp);
                ftemp=(ftemp * 9 / 5) + 32;  // units in fahreneit
                wu_tempf=Round1Dec(ftemp);
                ftemp=Round0Dec(ftemp);
                if (ftemp < 0) {
                  isAPRS_tempf_negative=true;
                  APRS_tempfpos=abs(ftemp);
                }else{
                  isAPRS_tempf_negative=false;
                  APRS_tempfpos=ftemp;
                }
                if (isDebug) {Serial.print(F(" Tempf="));}
                if (isDebug) {Serial.print(APRS_tempfpos);}
                if ((isDebug) && (isAPRS_tempf_negative==true)) {Serial.print(F("-"));}
                if ((isDebug) && (isAPRS_tempf_negative==false)) {Serial.print(F("+"));}
                if (isDebug) {Serial.print(F(" WU_TempC="));}
                if (isDebug) {Serial.print(wu_temp_c,1);}
                if (isDebug) {Serial.print(F(" WU_F="));}
                if (isDebug) {Serial.print(wu_tempf,1);}
               }else{
                isAPRS_tempf_negative=false;
                APRS_tempfpos=0;
                wu_tempf=0;
                wu_temp_c=0;
              }

              remote_wind_ok=false;  // bit 5, +3byte: 1byte Heading in 360/256 degree, 1byte speed and 1byte gusts in 0.2km/h (each: bit 7 scale 5x or 1x, bit 0-6)
              if (bitRead(str[4],5) == 1){
                remote_wind_ok=true;
                btemp=str[pointer];  //          dir
                pointer++;
                itemp=(byte)btemp;
                ftemp=(float)(itemp * 1.40625);  // units are 360/256=1.40625
                APRS_winddir=Round0Dec(ftemp);
                if (APRS_winddir == 360) {APRS_winddir=0;}
                wu_winddir=APRS_winddir;
                if (isDebug) {Serial.print(F(" WindDir="));}
                if (isDebug) {Serial.print(APRS_winddir);}

                windspeedscaling=1;  //          speed
                ch=str[pointer];
                pointer++;
                if (bitRead(ch,7) == 1) {windspeedscaling=5;}
                bitWrite(ch,7,0);  // obfuscates scaling bit 7
                btemp=ch;
                itemp=(byte)btemp;
                ftemp=(float)(itemp * 0.2);  // units are 0.2km/h
                ftemp=(ftemp * windspeedscaling);
                ftemp=Round2Dec(ftemp);
                wu_windspeedkmh=ftemp;
                wu_windspeedkmh=Round1Dec(wu_windspeedkmh);
                wu_windspeedmph=(ftemp * 0.621371);
                wu_windspeedmph=Round1Dec(wu_windspeedmph);
                APRS_windspeedmph=Round0Dec(wu_windspeedmph);
                if (isDebug) {Serial.print(F(" WindSpeedmph="));}
                if (isDebug) {Serial.print(APRS_windspeedmph,1);}
                if (isDebug) {Serial.print(F(" WU_kmh="));}
                if (isDebug) {Serial.print(wu_windspeedkmh,1);}
                if (isDebug) {Serial.print(F(" WU_mph="));}
                if (isDebug) {Serial.print(wu_windspeedmph,1);}

                windgustscaling=1;  //           gust
                ch=str[pointer];
                pointer++;
                if (bitRead(ch,7) == 1) {windgustscaling=5;}
                bitWrite(ch,7,0);  // obfuscates scaling bit 7
                btemp=ch;
                itemp=(byte)btemp;
                ftemp=(float)(itemp * 0.2);  // units are 0.2km/h
                ftemp=(ftemp * windgustscaling);
                ftemp=Round2Dec(ftemp);
                wu_windgustkmh=ftemp;
                wu_windgustkmh=Round1Dec(wu_windgustkmh);
                wu_windgustmph=(ftemp * 0.621371);
                wu_windgustmph=Round1Dec(wu_windgustmph);

                w10g=(wind10gustmph[0] + 1);  // top gust mph last 10 minutes
                if (w10g>10) {w10g=1;}
                wind10gustmph[w10g]=wu_windgustmph;
                wind10gustmph[0]=w10g;
                wu_windgustmph_10m=0;
                for (byte c=1;c<=10;c++){
                  if (wind10gustmph[c] > wu_windgustmph_10m) {wu_windgustmph_10m = wind10gustmph[c];}
                }
                wu_windgustmph_10m=Round1Dec(wu_windgustmph_10m);

                APRS_windgustmph=Round0Dec(wu_windgustmph_10m);
                if (isDebug) {Serial.print(F(" WindGustmph="));}
                if (isDebug) {Serial.print(APRS_windgustmph);}
                if (isDebug) {Serial.print(F(" WU_kmh="));}
                if (isDebug) {Serial.print(wu_windgustkmh,1);}
                if (isDebug) {Serial.print(F(" WU_mph="));}
                if (isDebug) {Serial.print(wu_windgustmph,1);}
                if (isDebug) {Serial.print(F(" WU_mph(10m)="));}
                if (isDebug) {Serial.print(wu_windgustmph_10m,1);}
               }else{
                APRS_winddir=0;
                APRS_windspeedmph=0;
                APRS_windgustmph=0;
                wu_winddir=0;
                wu_windspeedkmh=0;
                wu_windgustkmh=0;
              }

              remote_humidity_ok = false;  // bit 4, Humidity, +1byte: in 0.4
              if (bitRead(str[4],4) == 1){
                remote_humidity_ok=true;
                btemp=str[pointer];
                pointer++;
                itemp=(byte)btemp;
                ftemp=(float)(itemp * 4.0 / 10.0);  // units are 0.4: es. value 125 -> (125*4/10) = 50% humidity
                ftemp=Round0Dec(ftemp);
                APRS_humidity=ftemp;
                wu_humidity=ftemp;
                if (isDebug) {Serial.print(F(" Humidity="));}
                if (isDebug) {Serial.println(APRS_humidity);}
               }else{
                APRS_humidity=1;
                wu_humidity=1;
              }

              if ((remote_tempf_ok) && (remote_humidity_ok)) {  // wunderground dewptf
                remote_dewptf_ok=true;
                dpA=(wu_humidity / 100.0F);
                dpB=(17.27 * wu_temp_c);
                dpC=(237.3 + wu_temp_c);
                dpNC=(log(dpA) + (dpB / dpC)) / 17.27;
                dewptC=(237.3 * dpNC) / (1 - dpNC);
                wu_dewptf=(dewptC * 9 / 5) + 32;
                wu_dewptf=Round1Dec(wu_dewptf);
                if (isDebug) {Serial.print(F("WU_DewPointC="));}
                if (isDebug) {Serial.print(dewptC,1);}
                if (isDebug) {Serial.print(F(" WU_F="));}
                if (isDebug) {Serial.print(wu_dewptf,1);}
               }else{
                remote_dewptf_ok=false;
              }

              remote_baromin_ok = false;  //  bit 3, Barometric pressure normailized, +2byte: in 10Pa, offset by 430hPa, unsigned little endian (hPa-430)*10
              if (bitRead(str[4],3) == 1){
                remote_baromin_ok = true;
                btemp=str[pointer];
                pointer++;
                APRS_barom10hpa=0;
                if (isDebug) {Serial.print(F(" Baromhpa="));}
                if (isDebug) {Serial.print(APRS_barom10hpa);}
               }else{
                APRS_barom10hpa=0;
              }

              sensorBatteryStatus=0; // was "Support for Remote conf.", now sensor battery status 0=low 1=ok, bit 2
              if (bitRead(str[4],2) == 1){
                 sensorBatteryStatus=1;
                 if (isDebug) {Serial.print(F(" SensorBatt=OK"));} 
              }else{
                 if (isDebug) {Serial.print(F(" SensorBatt=LOW"));} 
              }
              
              remote_reboots_ok = false;  //   bit 1, number of MCU reboots by watchdog/other unusual way +1byte/bits 7-4 
              if (bitRead(str[4],1) == 1){
                remote_reboots_ok = true;
                ch=0;
                if (bitRead(str[pointer],7) == 1) {bitWrite(ch,3,1);}
                if (bitRead(str[pointer],6) == 1) {bitWrite(ch,2,1);}
                if (bitRead(str[pointer],5) == 1) {bitWrite(ch,1,1);}
                if (bitRead(str[pointer],4) == 1) {bitWrite(ch,0,1);}
                btemp=ch;
                itemp=(byte)btemp;
                APRS_reboots=itemp;
                if (isDebug) {Serial.print(F(" Reboots="));}
                if (isDebug) {Serial.print(APRS_reboots);}
              }else{
                APRS_reboots=0;
              }
              remote_battery_ok = false;  //   together with bit 1, battery voltage = (2.8 + (N/10)) (N=value bits 3-0)
              if (bitRead(str[4],1) == 1){
                remote_battery_ok = true;
                ch=0;
                if (bitRead(str[pointer],3) == 1) {bitWrite(ch,3,1);}
                if (bitRead(str[pointer],2) == 1) {bitWrite(ch,2,1);}
                if (bitRead(str[pointer],1) == 1) {bitWrite(ch,1,1);}
                if (bitRead(str[pointer],0) == 1) {bitWrite(ch,0,1);}
                pointer++;
                btemp=ch;
                itemp=(byte)btemp;
                APRS_battery10=(28 + itemp);
                if (isDebug) {Serial.print(F(" Battery="));}
                if (isDebug) {Serial.print((APRS_battery10 / 10.0),1);}
              }else{
                APRS_battery10=0;
              }
              
              remote_rainin_ok = false;  // n.u.
              APRS_rain100in=0;
              APRS_raindaily100in=0;
              
              remote_solarradiationwm2_ok = false;  // n.u.
              APRS_solarradiationwm2=0;

              if (isDebug) {Serial.println(F(""));}

              // Telemetry in comment ---------------------------
              telemetry_in_comment = "";
              telemetry_in_comment +="T";
              if (failures_NTP <= 9) {
                telemetry_in_comment += String(failures_NTP);
               }else{
                telemetry_in_comment += "!";
                if (isDebug == true) {Serial.println(F("** Too many NTP failures, STOPPED! **"));}
                isTooFailures=true;
              }
              telemetry_in_comment += "A";
              if (failures_APRS <= 9) {
                telemetry_in_comment += String(failures_APRS);
               }else{
                telemetry_in_comment += "!";
                if (isDebug == true) {Serial.println(F("** Too many APRS failures, STOPPED! **"));}
                isTooFailures=true;
              }
              telemetry_in_comment += "R";
              if (reboot_count <= 9) {
                telemetry_in_comment += String(reboot_count);
               }else{
                telemetry_in_comment += "!";
                if (isDebug == true) {Serial.println(F("** Too many unusual reboot by watchdog/other, PLEASE CHECK! **"));}
                isTooFailures=true;
              }
              telemetry_in_comment += "W";
              if (failures_WUNDER <= 9) {
                telemetry_in_comment += String(failures_WUNDER);
               }else{
                telemetry_in_comment += "!";
                if (isDebug == true) {Serial.println(F("** Too many Wunderground failures, STOPPED! **"));}
                isTooFailures=true;
              }
              telemetry_in_comment += "N";
              if (failures_WIFI <= 9) {
                telemetry_in_comment += String(failures_WIFI);
               }else{
                telemetry_in_comment += "!";
                if (isDebug == true) {Serial.println(F("** Too many WiFi failures, STOPPED! **"));}
                isTooFailures=true;
              }
              telemetry_in_comment += "E";
              if (failures_LEN <= 99) {
                telemetry_in_comment += String(failures_LEN);
               }else{
                telemetry_in_comment += "!!";
                if (isDebug == true) {Serial.println(F("** Too many packet LEN failures, STOPPED! **"));}
                isTooFailures=true;
              }

              telemetry_in_comment += "I";
              trimmed=String(abs(rssi),0);
              trimmed.trim();
              telemetry_in_comment += String(trimmed);
              
              telemetry_in_comment += "V";
              rv = (float)(APRS_battery10 / 10.0);
              telemetry_in_comment += String(APRS_battery10);
              if (APRS_battery10 < 35) {
                if (isDebug == true) {Serial.println(F("** Remote battery voltage LOW! **"));}
              }
              telemetry_in_comment += "S";
              if (APRS_reboots <= 9) {
                telemetry_in_comment += String(APRS_reboots);
               }else{
                telemetry_in_comment += "!";
                if (isDebug == true) {Serial.println(F("** Many remote MCU reboots (watchdog/other), PLEASE CHECK! **"));}
              }
              if (sensorBatteryStatus == 1){
                telemetry_in_comment += "+";
               }else{
                telemetry_in_comment += "-";
              }
              
              if (isDebug) {Serial.print(F("Telemetry in comment: "));}
              if (isDebug) {Serial.println(telemetry_in_comment);}

              if (isTooFailures==true) {
                while(true) {  // stop running because of many failures (it will cause a restart by watchdog soon)
                  display.invertDisplay(true);
                  delay(1000);
                  display.invertDisplay(false);
                  delay(1000);  
                }
              }
              //----------------------------------------------------------------- uploading to Wunderground.com
              actualWundMillis=millis();
              if (prevWundMillis > actualWundMillis) {  // manage millis() rollover
                prevWundMillis=actualWundMillis;
                actualWundMillis=prevWundMillis + wunderUpdateRate;
              }
              if ((isWUNDERenabled == true) && ((prevWundMillis + wunderUpdateRate) <= actualWundMillis)) {
                wuPacket = "GET /weatherstation/updateweatherstation.php?";
                wuPacket += "ID=";
                wuPacket += mywunderID;
                wuPacket += "&PASSWORD=";
                wuPacket += mywunderPASS;
                wuPacket += "&dateutc=now";
                if (remote_wind_ok==true) {
                  trimmed=String(wu_winddir,0);
                  trimmed.trim();
                  wuPacket += "&winddir=" + trimmed;
                  wuPacket += "&windspeedmph=" + String(wu_windspeedmph,1);
                  wuPacket += "&windgustmph=" + String(wu_windgustmph,1);
                  //wuPacket += "&windgustmph_10m=" + String(wu_windgustmph_10m,1);
                }
                if (remote_tempf_ok==true) {wuPacket += "&tempf=" + String(wu_tempf,1);}
                if (remote_dewptf_ok==true) {wuPacket += "&dewptf=" + String(wu_dewptf,1);}
                if (remote_humidity_ok==true) {
                  trimmed=String(wu_humidity,0);
                  trimmed.trim();
                  wuPacket += "&humidity=" + trimmed;
                }
                wuPacket += "&softwaretype=ABW%20version1.0&action=updateraw";
                wuPacket += " HTTP/1.1\r\n";
                wuPacket += "Host: ";
                wuPacket += wuserver;
                wuPacket += "\r\n";
                wuPacket += "Connection: close\r\n\r\n";
                if (isDebug == true) {Serial.println(F("------------------------------------------------"));}
                if (isDebug == true) {Serial.println(F("WuPacket ============ START"));}
                if (isDebug == true) {Serial.print(F("''"));}
                if (isDebug == true) {Serial.print(wuPacket);}
                if (isDebug == true) {Serial.println(F("''"));}
                if (isDebug == true) {Serial.println(F("WuPacket ============ END"));}
                connectWiFi();
                sendWUNDERnet();
                WiFidisconnect();
                prevWundMillis=millis();
              }

              //------------------------------------------------------------------------- uploading to aprs.fi
              actualAPRSMillis=millis();
              if (prevAPRSMillis > actualAPRSMillis) {  // manage millis() rollover
                prevAPRSMillis=actualAPRSMillis;
                actualAPRSMillis=prevAPRSMillis + APRSUpdateRate;
              }
              if ((isAPRSenabled == true) && ((prevAPRSMillis + APRSUpdateRate) <= actualAPRSMillis)) {
                  if (isDebug) {Serial.println(F("Preparing data for APRS"));}  
                  APRSPacket="";
                  APRSPacket += myAPRScallsign;
                  APRSPacket += ">APRS,TCPIP*:";
                  APRSPacket += "@";
                  if (APRS_day < 10) {APRSPacket += "0";}
                  APRSPacket += String(APRS_day);
                  if (APRS_hour < 10) {APRSPacket += "0";}
                  APRSPacket += String(APRS_hour);
                  if (APRS_minute < 10) {APRSPacket += "0";}
                  APRSPacket += String(APRS_minute);
                  APRSPacket += "z";
                  APRSPacket += myAPRSlatLon;
                  if (remote_wind_ok == true) {
                      APRSPacket += "_";
                      if ((APRS_winddir > 9) && (APRS_winddir <100)) {APRSPacket += "0";}
                      if (APRS_winddir < 10) {APRSPacket += "00";}
                      APRSPacket += String(APRS_winddir);
                      APRSPacket += "/";
                      if ((APRS_windspeedmph > 9) && (APRS_windspeedmph <100)) {APRSPacket += "0";}
                      if (APRS_windspeedmph < 10) {APRSPacket += "00";}
                      APRSPacket += String(APRS_windspeedmph);
                      APRSPacket += "g";
                      if ((APRS_windgustmph > 9) && (APRS_windgustmph <100)) {APRSPacket += "0";}
                      if (APRS_windgustmph < 10) {APRSPacket += "00";}
                      APRSPacket += String(APRS_windgustmph);
                   }else{
                      APRSPacket += "_.../...g...";
                  }
                  if (remote_tempf_ok==true) {
                      APRSPacket += "t";
                      if (isAPRS_tempf_negative == true) {
                        if (APRS_tempfpos < 10) {APRSPacket += "-0";}
                        if (APRS_tempfpos > 9) {APRSPacket += "-";}
                       }else{
                        if ((APRS_tempfpos > 9) && (APRS_tempfpos <100)) {APRSPacket += "0";}
                        if (APRS_tempfpos < 10) {APRSPacket += "00";}
                      }
                      APRSPacket += String(APRS_tempfpos);
                   }else{
                      APRSPacket += "t...";
                  }
                  if (remote_rainin_ok==true) {
                      APRSPacket += "r";
                      if ((APRS_rain100in > 9) && (APRS_rain100in <100)) {APRSPacket += "0";}
                      if (APRS_rain100in < 10) {APRSPacket += "00";}
                      APRSPacket += String(APRS_rain100in);
                      //APRSPacket += "p...";
                      APRSPacket += "P";
                      if ((APRS_raindaily100in > 9) && (APRS_raindaily100in <100)) {APRSPacket += "0";}
                      if (APRS_raindaily100in < 10) {APRSPacket += "00";}
                      APRSPacket += String(APRS_raindaily100in);
                   }else{
                      APRSPacket += "r...P...";
                  }
                  if (remote_humidity_ok==true) {
                      APRSPacket += "h";
                      if (APRS_humidity == 0) {APRS_humidity = 1;}
                      if (APRS_humidity == 100) {APRS_humidity = 0;}
                      if (APRS_humidity < 10) {APRSPacket += "0";}
                      APRSPacket += String(APRS_humidity);
                   }else{
                      APRSPacket += "h..";
                  }
                  if (remote_baromin_ok==true) {
                      APRSPacket += "b";
                      if (APRS_barom10hpa < 10000) {APRSPacket += "0";}
                      APRSPacket += String(APRS_barom10hpa);
                   }else{
                      APRSPacket += "b.....";
                  }
                  if (remote_solarradiationwm2_ok == true) {
                      APRSPacket += "L";
                      if (APRS_solarradiationwm2 > 999) {APRS_solarradiationwm2 = 999;}
                      if ((APRS_solarradiationwm2 > 9) && (APRS_solarradiationwm2 <100)) {APRSPacket += "0";}
                      if (APRS_solarradiationwm2 < 10) {APRSPacket += "00";}
                      APRSPacket += String(APRS_solarradiationwm2);
                   }else{
                      APRSPacket += "L...";
                  }
                  APRSPacket += "PWS_Lora32 ";
                  APRSPacket += "(";
                  APRSPacket += telemetry_in_comment;
                  APRSPacket += ")";
                  APRSPacket += "\n";
                  if (isDebug == true) {
                    Serial.println(F("------------------------------------------------"));
                    Serial.println(F("APRS Packet ============ START"));
                    Serial.print(F("''"));
                    Serial.print(APRSPacket);
                    Serial.println(F("''"));
                    Serial.println(F("APRS Packet ============ END"));
                    Serial.print(F("APRS values: "));
                    Serial.print(APRS_day);
                    Serial.print(F(" "));
                    Serial.print(APRS_hour);
                    Serial.print(F(" "));
                    Serial.print(APRS_minute);
                    Serial.print(F(" "));
                    Serial.print(APRS_winddir);
                    Serial.print(F(" "));
                    Serial.print(APRS_windspeedmph);
                    Serial.print(F(" "));
                    Serial.print(APRS_windgustmph);
                    Serial.print(F(" "));
                    Serial.print(APRS_tempfpos);
                    Serial.print(F(" "));
                    if (isAPRS_tempf_negative == true) {Serial.print("true");}
                    if (isAPRS_tempf_negative == false) {Serial.print("false");}
                    Serial.print(F(" "));
                    Serial.print(APRS_rain100in);
                    Serial.print(F(" "));
                    Serial.print(APRS_raindaily100in);
                    Serial.print(F(" "));
                    Serial.print(APRS_humidity);
                    Serial.print(F(" "));
                    Serial.print(APRS_barom10hpa);
                    Serial.print(F(" "));
                    Serial.print(APRS_solarradiationwm2);
                    Serial.print(F(" "));
                    Serial.print(APRS_battery10);
                    Serial.print(F(" "));
                    Serial.print(APRS_reboots);
                    Serial.print(F(" "));
                    Serial.print(sensorBatteryStatus);
                    Serial.println(F(" "));
                  }     
                  connectWiFi();
                  sendAPRSnet();
                  WiFidisconnect();
                  prevAPRSMillis=millis();
                  if (isDebug) {Serial.println(F("APRS job finished."));}  
              }//if((prevAPRSMillis+APRSUpdateRate)<=actualAPRSMillis)
              //------------------------------------------------------------------------- update NTP time
              actualNTPMillis=millis();
              if (prevNTPMillis > actualNTPMillis) {  // manage millis() rollover
                prevNTPMillis=actualNTPMillis;
                actualNTPMillis=prevNTPMillis + updateNTPRate;
              }
              if (((prevNTPMillis + updateNTPRate) <= actualNTPMillis) || ((isTimeUpdated==false))) {
                  connectWiFi();
                  TimeNTPUpdate();
                  WiFidisconnect();
              }
              min2upd=((actualNTPMillis/1.0F-(prevNTPMillis/1.0F+updateNTPRate/1.0F))/60000.0F);
              if (isDebug == true) {Serial.print(F("Next NTP Time update min = "));}
              if (isDebug == true) {Serial.println(min2upd,0);}
              delay(10);
           }else{
              if (isDebug) {Serial.println(F("** ID, TYPE or LEN not accepted **"));}
              if ((IDRX == IDtoReceive) && (len != mustLen)) {failures_LEN++;}
          }//if(IDRX==IDtoReceive)
      }//if(state==RADIOLIB_ERR_NONE)
      // Radio to listen mode
      digitalWrite(ledPin, LOW);
      radio.startReceive();
  }//if(receivedFlag==true)
  //----------------------------------------------------------------- watchdog feeding
...

This file has been truncated, please download it to see its full contents.

Arduino code, TRANSMITTER (remote side)

Arduino
/*
 * 
 *  ard-weather868transmitter by Marco Zonca 12/2025 version 1.0
 *  
 *  deep-sleep mode enabled, FANET like type 4 (service -> weather)
 *  data received from Bresser 5in1 sensors (protocol 6in1, 2 packets instead of one)
 *  
 *  
 * 
 * 
  - this sketch is derived from the decoding example code made by Matthias Prinke (read copyright below);
    I used the BresserWeatherSensorReceiver library ver. 0.37.0;
    
  - before compiling you have to personalize WeatherSensorCfg.h as specified by the author; I modified
    the following line numbers commenting/uncommenting as necessary; very important is the line number 93 where you could
    enter your station ID if you have more than one around, if you leave it empty you will receive any station ID, but
    if you ENTER IT PLEASE NOTE that it will change every station reset or battery change so you need to keep it
    updated manually! I suggest to leave it empty as is here below;

      89:  #define SENSOR_IDS_EXC { }
      93:  #define SENSOR_IDS_INC { }
      101: //#define WIND_DATA_FIXEDPOINT 
      105: //#define BRESSER_5_IN_1
      106: #define BRESSER_6_IN_1
      107: //#define BRESSER_7_IN_1
      108: //#define BRESSER_LIGHTNING
      109: //#define BRESSER_LEAKAGE
      151: #define ARDUINO_TTGO_LoRa32_V21new

   12.01.26   - added deep-sleep mode time 60" to save around 75% of battery
   13.01.26   - added random(45,75) deep-sleep time
              - added back to deep-sleep if battery < 3.2v, with wakeup time 1/2 hour (1800")
              - removed unused code (off display, reset counters at 00:00, some delays)
              - added random time TX delay (1,2000) millis
   16.01.2026 - changed txPacket variable from String to uint8_t
   18.02.2026 - added instrument ID in the form "XXYYZZ", this value is to specify as filter in the Receiver side
   
 */

#define RADIOLIB_GODMODE 1  // Must be BEFORE the include, needed to modify registers of RadioLib

#include <RadioLib.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/TomThumb.h>
#include "WeatherSensorCfg.h"
#include "WeatherSensor.h"
#include "TimeLib.h"
#include <esp_task_wdt.h>
#include <Preferences.h>
#include <rom/rtc.h>
#include "InitBoard.h"
#include <esp_mac.h> // necessary for esp_read_mac

#define OLED_RESET -1
#define SCREEN_WIDTH 128    // OLED display width, in pixels
#define SCREEN_HEIGHT 32    // OLED display height, in pixels
#define SCREEN_ADDRESS 0x3C // See datasheet for Address, 0x3C for 128x32
#define FRQ_CORRECTION 0.014000
#define BATTERY_PIN 35
#define WDT_TIMEOUT 60000  // watchdog 60", in millis
#define SENSOR_TIMEOUT 40000  // bresser 40", in millis

boolean isDebug=true;
const double FANET_latitude_deg = 40.12345;  // latitude of the remote station
const double FANET_longitude_deg = 11.01234; // longitude of the remote station
double txrxFrequency = 868.700000;  // working frq in Mhz
byte mustLen = 17;  // tx packet length
const byte ledPin=25;
uint8_t txPacket[256];
uint8_t mac[6];
char instrumentID[7];

double FANET_temp_c = 0;
double FANET_winddir = 0;
double FANET_windspeedkmh = 0;
double FANET_windgustkmh = 0;
double FANET_humidity = 0;
int FANET_reboots=0;
double FANET_battery=0;
float v = 0;
char Chrs=0;
unsigned long lasttx=0;
String elaps="";
double fd=0;
long fl=0;
int fi=0;
byte fb=0;
float ff=0;
String TV[6]={"","","","","",""};  // 0-4 = actual display, 5 = next line to show
byte cHH=0;
byte cMM=0;
unsigned int reboot_count=0;
int decode_status=0;
uint32_t timeSLM=0;
unsigned long timeRNDtx=0;

unsigned long prevSensorMillis = 0;
unsigned long actualSensorMillis = 0;
const unsigned long updateSensorRate = (1 * 1000); // 1", try Bresser sensors update rate
unsigned long sensorTime=0;

int sensorBatteryStatus=0;

time_t epoch = 0;

char TX_header0=0;
char TX_header1=0;
char TX_header2=0;
char TX_header3=0;
char TX_latitude1=0;
char TX_latitude2=0;
char TX_latitude3=0;
char TX_longitude1=0;
char TX_longitude2=0;
char TX_longitude3=0;
char TX_serviceheader=0;
char TX_temp_c=0;
char TX_winddir=0;
char TX_windspeedkmh=0;
char TX_windgustkmh=0;
char TX_humidity=0;
char TX_reboots_battery=0;

boolean bresser_tempf_ok=false;
boolean bresser_humidity_ok=false;
boolean bresser_wind_ok=false;
boolean bresser_solarradiationwm2_ok=false;
boolean bresser_rainin_ok=false;
boolean bresser_UV_ok=false;

// Pins for LilyGo LoRa32 v1.6.1 (Original ESP32 + SX1276)
// NSS, DIO0, RST, DIO1
SX1276 radiofanet = new Module(18, 26, 23, 33); 
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Preferences preferences;
WeatherSensor weatherSensor;

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println(F(""));
  Serial.println(F(""));
  Serial.println(F("----------------------Starting----------------------"));
  Serial.println(F(""));
  Serial.println(F("SERIAL monitor ok"));
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  elaps.reserve(4);
  TV[0].reserve(60);
  TV[1].reserve(60);
  TV[2].reserve(60);
  TV[3].reserve(60);
  TV[4].reserve(60);
  TV[5].reserve(60);

  Wire.begin(OLED_SDA, OLED_SCL);
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("OLED allocation failed"));
   }else{
    Serial.println(F("OLED display ok"));
  }

  epoch=967680060;  // initial datetime: 31/08/2000 00:01:00 in epoch format 
  setTime(epoch);

  // Configure ADC attenuation for 0-3.3V range for pin 35, battery
  analogSetPinAttenuation(35, ADC_11db);  
  v = readBatteryVoltage();
  Serial.print(F("Battery voltage: ")); Serial.println(v,2);
  Serial.println("");

  // watchdog activation
  esp_task_wdt_config_t twdt_config = {
      .timeout_ms = WDT_TIMEOUT,
      .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,    // Bitmask of all cores
      .trigger_panic = true,
  };
  esp_task_wdt_deinit(); // wdt is enabled by default, so we need to deinit it first
  esp_task_wdt_init(&twdt_config); // panic enabled so ESP32 restarts
  esp_task_wdt_add(NULL); // add current thread to WDT watch
  Serial.println(F("* Watchdog active *"));

  // reboot counter due to watchdog or unusual way
  preferences.begin("reboot", false);  // reboot counter and why, false means r/w
    reboot_count = preferences.getUInt("count", 0);
    RESET_REASON reason = rtc_get_reset_reason(0); // Core 0
    if ((reason != POWERON_RESET) && (reason != DEEPSLEEP_RESET)) {
      if (isDebug) {Serial.println(F("* ALERT: Last reset caused by watchdog or unusual reason! *"));}
      switch (reason) {
        case 1:  if (isDebug) {Serial.println(F("POWERON_RESET, 1, Vbat power on reset")); break;}
        case 3:  if (isDebug) {Serial.println(F("SW_RESET, 3, Software reset")); break;}
        case 4:  if (isDebug) {Serial.println(F("OWDT_RESET, 4, Legacy watch dog reset")); break;}
        case 5:  if (isDebug) {Serial.println(F("DEEPSLEEP_RESET, 5, Deep Sleep reset")); break;}
        case 6:  if (isDebug) {Serial.println(F("SDIO_RESET, 6, Reset by SLC module")); break;}
        case 7:  if (isDebug) {Serial.println(F("TG0WDT_SYS_RESET, 7, Timer Group0 Watch dog reset")); break;}
        case 8:  if (isDebug) {Serial.println(F("TG1WDT_SYS_RESET, 8, Timer Group1 Watch dog reset")); break;}
        case 9:  if (isDebug) {Serial.println(F("RTCWDT_SYS_RESET, 9, RTC Watch dog reset")); break;}
        case 10: if (isDebug) {Serial.println(F("INTRUSION_RESET, 10, Instrusion tested to reset CPU")); break;}
        case 11: if (isDebug) {Serial.println(F("TGWDT_CPU_RESET, 11, Time Group reset CPU")); break;}
        case 12: if (isDebug) {Serial.println(F("SW_CPU_RESET, 12, Software reset CPU")); break;}
        case 13: if (isDebug) {Serial.println(F("RTCWDT_CPU_RESET, 13, RTC Watch dog Reset CPU")); break;}
        case 14: if (isDebug) {Serial.println(F("EXT_CPU_RESET, 14, for APP CPU, reset by PRO CPU")); break;}
        case 15: if (isDebug) {Serial.println(F("RTCWDT_BROWN_OUT_RESET, 15, Reset when the vdd voltage is not stable")); break;}
        case 16: if (isDebug) {Serial.println(F("RTCWDT_RTC_RESET, 16, RTC Watch dog reset digital core and rtc module")); break;}
        default: if (isDebug) {Serial.println(F("NO_MEAN, unknown reason")); break;}
      }
      reboot_count++;
      if (reboot_count > 9) {
        if (isDebug) {Serial.println(F("** Many watchdog/unusual reboots! PLEASE CHECK!! **"));}
        if (isDebug) {Serial.println(F("** Reboots counter needed to reset to zero! **"));}
        reboot_count=0;
      }
    preferences.putUInt("count", reboot_count);
    }  
  preferences.end();
  if (isDebug) {Serial.print(F("Actual watchdog/unusual reboot counter = "));}
  if (isDebug) {Serial.println(reboot_count);}
  FANET_reboots=reboot_count;

  // check battery, if low go to deep-sleep 1/2 hour and then retry (waiting for solar panel recharge)
  if (v < 3.20) {
      if (isDebug) {Serial.println(F("** Battery low during boot, go to deep-sleep for 1/2 hour (need recharging) **"));}
      esp_sleep_enable_timer_wakeup(1800ULL * 1000000ULL);
      esp_deep_sleep_start();   
  }

  // read MAC address of WiFi, take last 3 bytes as instrument ID
  // this ID must be used as filtering ID at Receiver side, so it will receive only this one
  esp_read_mac(mac, ESP_MAC_WIFI_STA);
  sprintf(instrumentID, "%02X%02X%02X", mac[3], mac[4], mac[5]);
  if (isDebug) {Serial.print(F("Instrument ID: "));}
  if (isDebug) {Serial.print(instrumentID);}
  if (isDebug) {Serial.println(F(", this ID must be used as filtering ID at Receiver side so it will receive only this one."));}
  
  // initial display
  TV[0] = "   868Mhz & Bresser Weather    ";
  TV[1] = "        TEST TRANSMITTER       ";
  TV[2] = "  Battery=";
  TV[2] += String(v,1);
  TV[2] +="  Resets=";
  TV[2] += String(reboot_count);
  TV[3] = "   searching...                ";
  TV[4] = "                               ";
  showDisplay();
}//setup()


void loop() {
      //========================================================================================== B R E S S E R  sensors
      actualSensorMillis=millis();
      if (prevSensorMillis == 0) {prevSensorMillis=millis();}
      if (prevSensorMillis > actualSensorMillis) {  // manage millis() rollover
        prevSensorMillis=actualSensorMillis;
        actualSensorMillis=prevSensorMillis + updateSensorRate;
      }
      if (((prevSensorMillis + updateSensorRate) <= actualSensorMillis)) {
          initBoard();
          weatherSensor.begin();
          if (isDebug) {Serial.println(F("Attempt to receive data from Bresser sensors until TIMEOUT"));}
          //----------------------------------------------------------------- read external sensors via radio 868Mhz
          sensorTime=millis();
          while ((sensorTime+SENSOR_TIMEOUT) >= millis()){
              int i=0;
              weatherSensor.clearSlots();
              decode_status = weatherSensor.getMessage();
              if ((decode_status == DECODE_OK) && (weatherSensor.sensor[i].w.uv_ok) &&  (weatherSensor.sensor[i].w.temp_ok) && (!weatherSensor.sensor[i].w.rain_ok) && (weatherSensor.sensor[i].w.humidity_ok)){ 
                  if (isDebug) {Serial.print(F("Bresser weather sensor actual ID="));}
                  if (isDebug) {Serial.println(weatherSensor.sensor[i].sensor_id,HEX);}
                  sensorBatteryStatus=weatherSensor.sensor[i].battery_ok;
                  if ((isDebug) && (sensorBatteryStatus == 1)) {Serial.println(F("Battery OK"));}
                  if ((isDebug) && (sensorBatteryStatus == 0)) {Serial.println(F("** Battery LOW! **"));}
        
                  if (weatherSensor.sensor[i].w.temp_ok) {  // fanet "temp_c"
                      bresser_tempf_ok=true;
                      if (isDebug == true) {Serial.print(F("Temperature C = "));}
                      if (isDebug == true) {Serial.println(weatherSensor.sensor[i].w.temp_c,1);}
                      FANET_temp_c=weatherSensor.sensor[i].w.temp_c;
                      FANET_temp_c=Round1Dec(FANET_temp_c);
                   }else{
                      bresser_tempf_ok=false;
                      if (isDebug == true) {Serial.println(F("* failed to read Bresser tempf, value not set *"));}
                      FANET_temp_c=0;
                  }
                  if (weatherSensor.sensor[i].w.humidity_ok) {  // fanet "humidity"
                      bresser_humidity_ok=true;
                      if (isDebug == true) {Serial.print(F("Humidity % = "));}
                      if (isDebug == true) {Serial.println(weatherSensor.sensor[i].w.humidity,1);}
                      FANET_humidity=weatherSensor.sensor[i].w.humidity;
                      FANET_humidity=Round0Dec(FANET_humidity);
                   }else{
                      bresser_humidity_ok=false;
                      if (isDebug == true) {Serial.println(F("* failed to read Bresser humidity, value not set *"));}
                      FANET_humidity=1;
                  }
                  if (weatherSensor.sensor[i].w.wind_ok) {  // FANET "windspeedkmh", "windgustkmh", "winddir"
                      bresser_wind_ok=true;
                      FANET_windspeedkmh=(weatherSensor.sensor[i].w.wind_avg_meter_sec * 3.6);
                      FANET_windspeedkmh=Round1Dec(FANET_windspeedkmh);
                      if (isDebug == true) {Serial.print(F("Wind km/h = "));}
                      if (isDebug == true) {Serial.println(FANET_windspeedkmh,1);}
        
                      FANET_windgustkmh=(weatherSensor.sensor[i].w.wind_gust_meter_sec * 3.6);
                      FANET_windgustkmh=Round1Dec(FANET_windgustkmh);
                      if (isDebug == true) {Serial.print(F("Wind gust km/h = "));}
                      if (isDebug == true) {Serial.println(FANET_windgustkmh,1);}
        
                      FANET_winddir=weatherSensor.sensor[i].w.wind_direction_deg;
                      FANET_winddir=Round0Dec(FANET_winddir);
                      if (isDebug == true) {Serial.print(F("Wind Direction deg = "));}
                      if (isDebug == true) {Serial.println(weatherSensor.sensor[i].w.wind_direction_deg,0);}
                     }else{
                      bresser_wind_ok=false;
                      if (isDebug == true) {Serial.println(F("* failed to read Bresser wind, values speed/gust/dir not set *"));}
                      FANET_windspeedkmh=0;
                      FANET_windgustkmh=0;
                      FANET_winddir=0;
                  }
        
                  //========================================================================================== F A N E T   T X
                  // Initialize for remote WX: 868.7MHz, 250kHz, SF7, CR8, SyncWord 0xF1, txpower 14 (5 to 20dBm, 5=3mW, 10=10mW 14=25mW, 20=100mW, P=10^(dBm/10), Preamble 8, rx 0=AGC
                  // Initialize for FANET: 868.2MHz, 250kHz, SF7, CR8, SyncWord 0xF1, txpower 14 (5 to 20dBm, 5=3mW, 10=10mW 14=25mW, 20=100mW, P=10^(dBm/10), Preamble 8, rx 0=AGC
                  // frequency Mhz, bandwidth, spreading factor 6-12, coding rate 5-8/8 (5 was 8), syncword, power, preamblelength, gain
                  Serial.println(F("RADIO initializing... "));
                  int state = radiofanet.begin((txrxFrequency - FRQ_CORRECTION), 250.0, 7, 8, 0xF1, 14, 8, 0);
                  radiofanet.explicitHeader(); // should be already as default value
                  radiofanet.setCRC(true); // should be already as default value
                
                  if (state == RADIOLIB_ERR_NONE) {
                    Serial.println(F("configured ok"));
                   } else {
                    Serial.print(F("failed, error code="));
                    Serial.println(state);
                    while (true);
                  }
                  // set power & max current
                  radiofanet.setOutputPower(14); 
                  radiofanet.setCurrentLimit(140);
                
                  // Critical: Apply GXAirCom's register settings for FANET ---------------
                  radiofanet.getMod()->SPIwriteRegister(0x31,0x43);  // was 0xC3
                  
                  uint8_t rawByte = radiofanet.getMod()->SPIreadRegister(0x31);
                  Serial.print(F("Register 0x31 content should be=0x43, now is: "));
                  Serial.print(F("0x"));
                  if (rawByte <16) {Serial.print(F("0"));}
                  Serial.println(rawByte,HEX);
                
                  rawByte = radiofanet.getMod()->SPIreadRegister(0x37);
                  Serial.print(F("Register 0x37 content should be=0x0A, now is: "));
                  Serial.print(F("0x"));
                  if (rawByte <16) {Serial.print(F("0"));}
                  Serial.println(rawByte,HEX);
                
                  rawByte = radiofanet.getMod()->SPIreadRegister(0x26);
                  Serial.print(F("Register 0x26 content should be=0x04, now is: "));
                  Serial.print(F("0x"));
                  if (rawByte <16) {Serial.print(F("0"));}
                  Serial.println(rawByte,HEX);
                  // Critical: Apply GXAirCom's register settings for FANET ---------------
        
                  //-------------------------------------------------------------------------------------- FANET packet type 4 weather
                  digitalWrite(ledPin, HIGH);
                  TX_header0=0;  // header byte 0
                  bitWrite(TX_header0,7,0);  // extended header (no)
                  bitWrite(TX_header0,6,0);  // forward (no)
                  bitWrite(TX_header0,5,0);  // type: bit 5 set=4 (service)
                  bitWrite(TX_header0,4,0);  // type: bit 4
                  bitWrite(TX_header0,3,0);  // type: bit 3
                  bitWrite(TX_header0,2,1);  // type: bit 2
                  bitWrite(TX_header0,1,0);  // type: bit 1
                  bitWrite(TX_header0,0,0);  // type: bit 0
                  TX_header1=0;  // header byte 1
                  TX_header1=mac[3];  // manufacturer, byte 0 of istrumentID
/*                bitWrite(TX_header1,7,1);  // manufacturer: bit 7 set=253=0xFD (unregistered) -----> Unique ID=50882, Fanet ID=FD,C6C2 
                  bitWrite(TX_header1,6,1);  //               bit 6
                  bitWrite(TX_header1,5,1);  //               bit 5
                  bitWrite(TX_header1,4,1);  //               bit 4
                  bitWrite(TX_header1,3,1);  //               bit 3
                  bitWrite(TX_header1,2,1);  //               bit 2
                  bitWrite(TX_header1,1,0);  //               bit 1
                  bitWrite(TX_header1,0,1);  //               bit 0
*/                TX_header2=0;  // header byte 2
                  TX_header2=mac[5];  // manufacturer serial, byte 2 of istrumentID
/*                bitWrite(TX_header2,7,1);  // id: bit 7 set=193=0xC2 (arbitrary...) byte 1 of 2, little endian
                  bitWrite(TX_header2,6,1);  //               bit 6
                  bitWrite(TX_header2,5,0);  //               bit 5
                  bitWrite(TX_header2,4,0);  //               bit 4
                  bitWrite(TX_header2,3,0);  //               bit 3
                  bitWrite(TX_header2,2,0);  //               bit 2
                  bitWrite(TX_header2,1,1);  //               bit 1
                  bitWrite(TX_header2,0,0);  //               bit 0
*/                TX_header3=0;  // header byte 3
                  TX_header3=mac[4];  // manufacturer serial, byte 1 of istrumentID
/*                bitWrite(TX_header3,7,1);  // id: bit 7 set=198=0xC6 (arbitrary...) byte 2 of 2, little endian
                  bitWrite(TX_header3,6,1);  //               bit 6
                  bitWrite(TX_header3,5,0);  //               bit 5
                  bitWrite(TX_header3,4,0);  //               bit 4
                  bitWrite(TX_header3,3,0);  //               bit 3
                  bitWrite(TX_header3,2,1);  //               bit 2
                  bitWrite(TX_header3,1,1);  //               bit 1
                  bitWrite(TX_header3,0,0);  //               bit 0
*/            
                  TX_serviceheader=0;  // service header byte 4
                  bitWrite(TX_serviceheader,7,0);  // gateway (no)
                  bitWrite(TX_serviceheader,6,1);  // temperature yes
                  bitWrite(TX_serviceheader,5,1);  // wind, yes, +3 bytes as direction, speed, gust
                  bitWrite(TX_serviceheader,4,1);  // humidity yes
                  bitWrite(TX_serviceheader,3,0);  // barometric normalized pressure (no)
                  bitWrite(TX_serviceheader,2,0);  // original was "remote config (yes=1/no=0)", now is sensor battery status 0=OK 1=LOW 
                  if ((sensorBatteryStatus == 1)) {bitWrite(TX_serviceheader,2,1);}
                  bitWrite(TX_serviceheader,1,1);  // state of charge, yes, bits 3-0 = (vbatt*10)-32, bits 7-4 = nr of reboots by MCU (watchdog)
                  bitWrite(TX_serviceheader,0,0);  // extender (no)
            
                  TX_latitude3=0;  // service header byte 5-7, position latitude byte 3 of 3, little endian
                  fd=(FANET_latitude_deg * 93206.0);
                  fl=Round0Dec(fd);
                  for (int i=7;i>=0;i--){
                    bitWrite(TX_latitude3,i,(bitRead(fl,i+16)));
                  }
                  TX_latitude2=0;  //                    byte 2 of 3
                  for (int i=7;i>=0;i--){
                    bitWrite(TX_latitude2,i,(bitRead(fl,i+8)));
                  }
                  TX_latitude1=0;  //                    byte 1 of 3
                  for (int i=7;i>=0;i--){
                    bitWrite(TX_latitude1,i,(bitRead(fl,i)));
                  }
            
                  TX_longitude3=0;  // service header byte 8-10, position longitude byte 3 of 3, little endian
                  fd=(FANET_longitude_deg * 46603.0);
                  fl=Round0Dec(fd);
                  for (int i=7;i>=0;i--){
                    bitWrite(TX_longitude3,i,(bitRead(fl,i+16)));
                  }
                  TX_longitude2=0;  //                    byte 2 of 3
                  for (int i=7;i>=0;i--){
                    bitWrite(TX_longitude2,i,(bitRead(fl,i+8)));
                  }
                  TX_longitude1=0;  //                    byte 1 of 3
                  for (int i=7;i>=0;i--){
                    bitWrite(TX_longitude1,i,(bitRead(fl,i)));
                  }
        
                  TX_temp_c=0;  // temperature C byte 11
                  if ((TX_temp_c < -60) || (TX_temp_c > 60)) {TX_temp_c=0;}
                  fd=(FANET_temp_c * 10.0 / 5.0);  //       units are in 0.5C, es. temp 5.5C -> 5.5*10/5 = 11 as value
                  fb=(byte)Round0Dec(fd);  //                     negative number rapresented as complement 2 (normal)
                  TX_temp_c=fb;
                            
                  TX_winddir=0;  // winddir byte 12
                  if ((FANET_winddir >= 360) || (FANET_winddir < 0)) {FANET_winddir=0;}
                  fd=(FANET_winddir / 1.40625);  // units are 360/256=1.40625
                  fb=(byte)Round0Dec(fd);
                  TX_winddir=fb;
            
                  TX_windspeedkmh=0;  // windspeed byte 13
                  if (FANET_windspeedkmh <= 25.4) {
                    fd=((FANET_windspeedkmh / 1.0) / 0.2);  // scaling = 1.0, units = 0.2kmh
                    fb=(byte)Round0Dec(fd);
                    TX_windspeedkmh=fb;
                    bitWrite(TX_windspeedkmh,7,0);  // set bit 7 as scaling 1
                  }
                  if ((FANET_windspeedkmh > 25.4) && (FANET_windspeedkmh <=127.0)) {
                    fd=((FANET_windspeedkmh / 5.0) / 0.2);  // scaling = 5.0, units = 0.2kmh
                    fb=(byte)Round0Dec(fd);
                    TX_windspeedkmh=fb;
                    bitWrite(TX_windspeedkmh,7,1);  // set bit 7 as scaling 5
                  }
            
                  TX_windgustkmh=0;  // windgust byte 14
                  if (FANET_windgustkmh <= 25.4) {
                    fd=((FANET_windgustkmh / 1.0) / 0.2);  // scaling = 1.0, units = 0.2kmh
                    fb=(byte)Round0Dec(fd);
                    TX_windgustkmh=fb;
                    bitWrite(TX_windgustkmh,7,0);  // set bit 7 as scaling 1
                  }
                  if ((FANET_windgustkmh > 25.4) && (FANET_windgustkmh <=127.0)) {
                    fd=((FANET_windgustkmh / 5.0) / 0.2);  // scaling = 5.0, units = 0.2kmh
                    fb=(byte)Round0Dec(fd);
                    TX_windgustkmh=fb;
                    bitWrite(TX_windgustkmh,7,1);  // set bit 7 as scaling 5
                  }
        
                  TX_humidity=0;  // humidity byte 15
                  if (FANET_humidity <= 0) {FANET_humidity=1;}
                  if (FANET_humidity >= 100) {FANET_humidity=99;}
                  fd=(FANET_humidity * 10.0 / 4.0);    // units are 0.4: es. humidity 50% -> (50*10/4) = 125 value
                  fb=(byte)Round0Dec(fd);
                  TX_humidity=fb;            
                  
                  TX_reboots_battery=0;  // charge byte 16 (bits 7-4 = nr of reboots, bits 3-0 = (battery*10)-28 )
                  fd=(float)v;
                  fd=Round1Dec(fd);
                  FANET_battery=fd;
                  fd=((fd * 10.0) - 28.0);
                  fb=(byte)Round0Dec(fd);
                  Chrs=(char)fb;
                  TX_reboots_battery=Chrs;
                  fb=(byte)FANET_reboots;
                  Chrs=(char)fb;
                  bitWrite(TX_reboots_battery,7,(bitRead(Chrs,3)));
                  bitWrite(TX_reboots_battery,6,(bitRead(Chrs,2)));
                  bitWrite(TX_reboots_battery,5,(bitRead(Chrs,1)));
                  bitWrite(TX_reboots_battery,4,(bitRead(Chrs,0)));

                  // build packet type 4 (weather)
                  txPacket[0] = TX_header0;
                  txPacket[1] = TX_header1;
                  txPacket[2] = TX_header2;
                  txPacket[3] = TX_header3;
                  txPacket[4] = TX_serviceheader;
                  txPacket[5] = TX_latitude1;
                  txPacket[6] = TX_latitude2;
                  txPacket[7] = TX_latitude3;
                  txPacket[8] = TX_longitude1;
                  txPacket[9] = TX_longitude2;
                  txPacket[10] = TX_longitude3;
                  txPacket[11] = TX_temp_c;
                  txPacket[12] = TX_winddir;
                  txPacket[13] = TX_windspeedkmh;
                  txPacket[14] = TX_windgustkmh;
                  txPacket[15] = TX_humidity;
                  txPacket[16] = TX_reboots_battery;
                  
                  Serial.print(F("Packet HEX : "));  // printout packet HEX
                  for (int k=0;k<mustLen;k++){
                    Chrs=(char)txPacket[k];
                    if (Chrs>15) {Serial.print(Chrs,HEX);}
                    if (Chrs<16 && Chrs>0) {Serial.print(F("0")); Serial.print(Chrs,HEX);}
                    if (Chrs==0) {Serial.print(F("00"));}
                  }
                  Serial.print(F(" ("));
                  Serial.print(mustLen);
                  Serial.println(F(" bytes)"));
                  
                  Serial.print(F("Packet BIN : "));  // printout packet BINARY
                  for (int k=0;k<mustLen;k++){
                    Chrs=(char)txPacket[k];
                    for (int i=7;i>=0;i--){
                      Serial.print(bitRead(Chrs,i));
                    }
                    if (k < (mustLen)-1) {Serial.print(F("-"));}
                  }
                  Serial.println();
            
                  cHH=hour();
                  cMM=minute();
                  Serial.print(F("Time="));
                  if (cHH < 10) {Serial.print(F("0"));}
                  Serial.print(cHH);
                  Serial.print(F(":"));
                  if (cMM < 10) {Serial.print(F("0"));}
                  Serial.print(cMM);
                  Serial.print(F(" Temp_c="));
                  Serial.print(FANET_temp_c,1);
                  Serial.print(F(" WindDir="));
                  Serial.print(FANET_winddir,0);
                  Serial.print(F(" WindSpeed="));
                  Serial.print(FANET_windspeedkmh,1);
                  Serial.print(F(" WindGust="));
                  Serial.print(FANET_windgustkmh,1);
                  Serial.print(F(" Humidity="));
                  Serial.print(FANET_humidity,0);
                  Serial.print(F(" Battery="));
                  Serial.print(FANET_battery,1);
                  Serial.print(F(" Nr.reboots="));
                  Serial.print(FANET_reboots);
                  Serial.println();
            
                  timeRNDtx=(random(1,4000));
                  Serial.print(F("Delay TX randomly (1-4000) for "));
                  Serial.print((timeRNDtx));
                  Serial.println(F(" millisec."));
                  delay(timeRNDtx);
                  transmitToTheAir();
                  delay(100);
                  
                  // prepare TV row for scrolling display
                  TV[3] = "   searching... OK, sent:       ";

                  TV[4]="";
                  if (cHH < 10) {TV[4] += "0";}  //                time now (HH:MM)
                  TV[4] += String(cHH);
                  TV[4] += ":";
                  if (cMM < 10) {TV[4] += "0";}
                  TV[4] += String(cMM);
                  TV[4] += "  ";
                  TV[4] += String(FANET_temp_c,1);  //             temperature C
                  TV[4] += "  ";
                  TV[4] += String(FANET_winddir,0);  //            dir
                  TV[4] += "  ";
                  TV[4] += String(FANET_windspeedkmh,1);  //       speed
                  TV[4] += "  ";
                  TV[4] += String(FANET_windgustkmh,1);  //        gust
                  TV[4] += "  ";
                  TV[4] += String(FANET_battery,1);  //            battery
                  TV[4] += "  ";
                  TV[4] += String(FANET_reboots);  //              reboots
                  TV[4] += "  ";
                  TV[4] += String(mustLen);  //          len
                  showDisplay();
                  digitalWrite(ledPin, LOW);
                  delay(2000);

                  // after transmission go to deep-sleep for around 1 minute (random 45-75")
                  timeSLM=random(45,75);
                  if (isDebug) {
                    Serial.print(F("Transmission done. Going to deep-sleep randomly (45-75) for "));
                    Serial.print(timeSLM);
                    Serial.println(F(" seconds."));
                  }
                  display.ssd1306_command(SSD1306_DISPLAYOFF);
                  esp_sleep_enable_timer_wakeup((uint64_t)timeSLM * 1000000);
                  esp_deep_sleep_start();    
                  
                  //continue;  // was exit while loop... but now it goes in deep sleep mode (see above)
              }//if((decode_status==DECODE_OK)&&(ws.sensor[i].w.uv_ok)&&...
          }//while ((sensorTime+SENSOR_TIMEOUT) <= millis())
          prevSensorMillis=millis();
      }//if (((prevSensorMillis + updateSensorRate) <= actualSensorMillis))
      //========================================================================================== W A T C H D O G  feeding
      esp_task_wdt_reset();  // feed the watchdog, every loop must be less than WDT_TIMEOUT or MCU will restart
      delay(1);  
}//loop()


void scrollDisplay(){  // 0-4 = actual display, 5 = next line to show
  display.clearDisplay();
  display.setFont(&TomThumb);  // small font
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0,6);              // Start at bottom-left corner
  display.cp437(true);                 // Use full 256 char 'Code Page 437' font
  for (byte t=0;t<5;t++) {
    TV[t]=TV[t+1];
    display.println(TV[t]);
  }
  display.display();
  delay(1);
}//scrollDisplay()


void showDisplay(){
  display.clearDisplay();
  display.setFont(&TomThumb);  // small font
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0,6);              // Start at bottom-left corner
  display.cp437(true);                 // Use full 256 char 'Code Page 437' font
  display.println(TV[0]);
  display.println(TV[1]);
  display.println(TV[2]);
  display.println(TV[3]);
  display.println(TV[4]);
  display.display();
  delay(1);
}//showDisplay()


float readBatteryVoltage() {
  // Read raw ADC value (0-4095 for 12-bit)
  int raw = analogRead(BATTERY_PIN);
  
  // Convert to voltage:
  // (raw / 4095.0) * 3.3V (ref) * 2 (voltage divider factor by internal resistors)
  float voltage = (raw / 4095.0) * 3.3 * 2.0 * 1.1;
  
  // Optional: Apply a small calibration offset if your multimeter shows different
  // voltage = voltage * 1.0244; 
  
  return voltage;
}//readBatteryVoltage()


int transmitToTheAir(){
  int state = radiofanet.transmit(txPacket,mustLen);
  delay(1000);
  if (state == RADIOLIB_ERR_NONE) {
    // the packet was successfully transmitted
    Serial.print(F("TX success, packet len="));
    Serial.println(mustLen);
    // print measured data rate
    Serial.print(F("Datarate:\t"));
    Serial.print(radiofanet.getDataRate());
    Serial.println(F(" bps"));
   } else {
    // some other error occurred
    Serial.print(F("TX failed, code="));
    Serial.print(state);
    Serial.print(F(", packet len="));
    Serial.println(mustLen);
  }
  delay(1);
  return state;
}//transmit()


// round 0 decimals
double Round0Dec(double f) {
  double y, d;
  y = f*1;
  d = y - (long)y;
  y = (double)(long)(f*1)/1;
  if (d >= 0.5) {
    y += 1;
   } else {
    if (d < -0.5) {
     y -= 1;
    }
  }
  return y;
}

// round 1 decimal
double Round1Dec(double f) {
  double y, d;
  y = f*10;
  d = y - (long)y;
  y = (double)(long)(f*10)/10;
  if (d >= 0.5) {
    y += 0.1;
   } else {
    if (d < -0.5) {
     y -= 0.1;
    }
  }
  return y;
}

// round 2 decimals
double Round2Dec(double f) {
  double y, d;
  y = f*100;
  d = y - (long)y;
  y = (double)(long)(f*100)/100;
  if (d >= 0.5) {
    y += 0.01;
   } else {
    if (d < -0.5) {
     y -= 0.01;
    } 
  }
  return y;
}

// round 3 decimals
double Round3Dec(double f) {
  double y, d;
  y = f*1000;
  d = y - (long)y;
  y = (double)(long)(f*1000)/1000;
  if (d >= 0.5) {
    y += 0.001;
   } else {
    if (d < -0.5) {
      y -= 0.001;
    }
  }
  return y;
}

Credits

Marco Zonca
21 projects • 59 followers
"From an early age I learned to not use pointers"

Comments