From c2e4dc1ed7f9995de6224f1e399e13fded90beb6 Mon Sep 17 00:00:00 2001
From: Jan Grewe <jan@faked.org>
Date: Sat, 1 Aug 2020 02:27:57 +0200
Subject: [PATCH] cleaned up code and output split into separate files add GPS
 timeout -> sleep

---
 .gitignore                       |   2 +-
 include/button.h                 |   2 +
 {src => include}/config.h.sample |   4 +-
 include/gps.h                    |   6 +
 include/lora.h                   |   5 +
 include/power.h                  |   8 +
 include/states.h                 |   4 +
 src/button.cpp                   |  20 ++
 src/gps.cpp                      |  63 ++++++
 src/lora.cpp                     | 169 +++++++++++++++
 src/main.cpp                     | 343 +++----------------------------
 src/power.cpp                    |  86 ++++++++
 12 files changed, 401 insertions(+), 311 deletions(-)
 create mode 100644 include/button.h
 rename {src => include}/config.h.sample (71%)
 create mode 100644 include/gps.h
 create mode 100644 include/lora.h
 create mode 100644 include/power.h
 create mode 100644 include/states.h
 create mode 100644 src/button.cpp
 create mode 100644 src/gps.cpp
 create mode 100644 src/lora.cpp
 create mode 100644 src/power.cpp

diff --git a/.gitignore b/.gitignore
index 95b4ce9..c334dbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@
 .vscode
 .travis.yml
 BikeBeam.code-workspace
-src/config.h
+include/config.h
diff --git a/include/button.h b/include/button.h
new file mode 100644
index 0000000..343c3a7
--- /dev/null
+++ b/include/button.h
@@ -0,0 +1,2 @@
+void buttonSetup();
+void buttonLoop();
\ No newline at end of file
diff --git a/src/config.h.sample b/include/config.h.sample
similarity index 71%
rename from src/config.h.sample
rename to include/config.h.sample
index 2606b70..d342c1d 100644
--- a/src/config.h.sample
+++ b/include/config.h.sample
@@ -1,4 +1,6 @@
-#define TIME_SLEEP 900   // 15 minutes
+#define SLEEP_SECONDS       900     // 15 minutes
+#define GPS_INT_SECONDS     1
+#define GPS_WAIT_SECONDS    120
 
 #define TTN_DEVEUI { 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01 };  // LSB
 #define TTN_APPEUI { 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0xD5, 0xB3, 0x70 };  // LSB
diff --git a/include/gps.h b/include/gps.h
new file mode 100644
index 0000000..f391c93
--- /dev/null
+++ b/include/gps.h
@@ -0,0 +1,6 @@
+#include <TinyGPS++.h>
+
+void gpsStatus();
+void gpsSetup();
+TinyGPSPlus getGps();
+int gpsCheck();
diff --git a/include/lora.h b/include/lora.h
new file mode 100644
index 0000000..6547485
--- /dev/null
+++ b/include/lora.h
@@ -0,0 +1,5 @@
+#include <CayenneLPP.h>
+
+void loraSetup();
+void loraLoop();
+int loraSend(CayenneLPP payload);
\ No newline at end of file
diff --git a/include/power.h b/include/power.h
new file mode 100644
index 0000000..e5f434c
--- /dev/null
+++ b/include/power.h
@@ -0,0 +1,8 @@
+#include <axp20x.h>
+
+void powerLed(axp_chgled_mode_t);
+float powerGetBattVoltage();
+void powerSetOutput(uint8_t channel, bool enabled);
+void powerStatus();
+void powerSetup();
+void powerSleep();
\ No newline at end of file
diff --git a/include/states.h b/include/states.h
new file mode 100644
index 0000000..253e2f1
--- /dev/null
+++ b/include/states.h
@@ -0,0 +1,4 @@
+#define STATE_INIT 1
+#define STATE_READY 2
+#define STATE_SENDING 3
+#define STATE_DONE 4
diff --git a/src/button.cpp b/src/button.cpp
new file mode 100644
index 0000000..bf6e407
--- /dev/null
+++ b/src/button.cpp
@@ -0,0 +1,20 @@
+#include <OneButton.h>
+#include "power.h"
+
+OneButton btn = OneButton(GPIO_NUM_38, true, true);
+
+static void buttonClick()
+{
+    Serial.println("\nButton pressed, going to sleep.");
+    powerSleep();
+}
+
+void buttonSetup()
+{
+    btn.attachClick(buttonClick);
+}
+
+void buttonLoop()
+{
+    btn.tick();
+}
\ No newline at end of file
diff --git a/src/gps.cpp b/src/gps.cpp
new file mode 100644
index 0000000..8d59ca0
--- /dev/null
+++ b/src/gps.cpp
@@ -0,0 +1,63 @@
+#include "config.h"
+#include "states.h"
+#include "power.h"
+#include <TinyGPS++.h>
+
+HardwareSerial GPS(1);
+TinyGPSPlus gps;
+extern int state;
+unsigned long gpsTimer;
+unsigned long gpsCount;
+
+TinyGPSPlus getGps()
+{
+    while (GPS.available())
+    {
+        gps.encode(GPS.read());
+    }
+    return gps;
+}
+
+void gpsStatus()
+{
+    gps = getGps();
+    Serial.println("\n########## GPS ##########");
+    Serial.printf("Latitude   : %f\n", gps.location.lat());
+    Serial.printf("Longitude  : %f\n", gps.location.lng());
+    Serial.printf("Satellites : %d\n", gps.satellites.value());
+    Serial.printf("Altitude   : %g m\n", gps.altitude.meters());
+    Serial.printf("Time       : %02d:%02d:%02d\n", gps.time.hour(), gps.time.minute(), gps.time.second());
+    Serial.printf("Speed      : %g\n", gps.speed.kmph());
+    Serial.println("#########################\n");
+}
+
+void gpsSetup()
+{
+    gpsTimer = millis();
+    gpsCount = 0;
+    GPS.begin(9600, SERIAL_8N1, 34, 12); // IO34: RX, IO12: TX
+    powerLed(AXP20X_LED_BLINK_1HZ);
+}
+
+void gpsCheck()
+{
+    if (millis() - gpsTimer >= 1000 * GPS_INT_SECONDS)
+    {
+        ++gpsCount;
+        Serial.printf("Waiting for GPS fix... (%lus)\n", gpsCount * GPS_INT_SECONDS);
+        gps = getGps();
+        if (gps.location.isValid())
+        {
+            Serial.printf("Got GPS fix after %lu seconds.\n", gpsCount * GPS_INT_SECONDS);
+            gpsStatus();
+            powerLed(AXP20X_LED_BLINK_4HZ);
+            state = STATE_READY;
+        }
+        else if (gpsCount * GPS_INT_SECONDS >= GPS_WAIT_SECONDS)
+        {
+            Serial.printf("No GPS fix after %d seconds, cancelling.\n", GPS_WAIT_SECONDS);
+            state = STATE_DONE;
+        }
+        gpsTimer = millis();
+    }
+}
\ No newline at end of file
diff --git a/src/lora.cpp b/src/lora.cpp
new file mode 100644
index 0000000..0ea3cb0
--- /dev/null
+++ b/src/lora.cpp
@@ -0,0 +1,169 @@
+#include "config.h"
+#include "states.h"
+#include "power.h"
+#include <Arduino_LoRaWAN_ttn.h>
+#include <lmic.h>
+#include <hal/hal.h>
+#include <CayenneLPP.h>
+
+extern int state;
+
+class cLoRaWAN : public Arduino_LoRaWAN_ttn
+{
+public:
+    cLoRaWAN(){};
+
+protected:
+    virtual bool GetOtaaProvisioningInfo(Arduino_LoRaWAN::OtaaProvisioningInfo *) override;
+    virtual void NetSaveFCntUp(uint32_t uFCntUp) override;
+    virtual void NetSaveFCntDown(uint32_t uFCntDown) override;
+    virtual void NetSaveSessionInfo(const SessionInfo &Info, const uint8_t *pExtraInfo, size_t nExtraInfo) override;
+};
+
+bool cLoRaWAN::GetOtaaProvisioningInfo(
+    OtaaProvisioningInfo *pInfo)
+{
+    static const uint8_t DEVEUI[8] = TTN_DEVEUI;
+    static const uint8_t APPEUI[8] = TTN_APPEUI;
+    static const uint8_t APPKEY[16] = TTN_APPKEY;
+
+    if (pInfo)
+    {
+        memcpy(pInfo->AppKey, APPKEY, sizeof(APPKEY));
+        memcpy(pInfo->DevEUI, DEVEUI, sizeof(DEVEUI));
+        memcpy(pInfo->AppEUI, APPEUI, sizeof(APPEUI));
+    }
+    return true;
+}
+
+void cLoRaWAN::NetSaveFCntDown(uint32_t uFCntDown)
+{
+}
+
+void cLoRaWAN::NetSaveFCntUp(uint32_t uFCntUp)
+{
+}
+
+void cLoRaWAN::NetSaveSessionInfo(
+    const SessionInfo &Info,
+    const uint8_t *pExtraInfo,
+    size_t nExtraInfo)
+{
+}
+
+const cLoRaWAN::lmic_pinmap pinMap = {
+    .nss = 18,
+    .rxtx = cLoRaWAN::lmic_pinmap::LMIC_UNUSED_PIN,
+    .rst = 23,
+    .dio = {26, 33, 32},
+};
+
+cLoRaWAN LoRaWAN{};
+
+void loraSetup()
+{
+    LoRaWAN.begin(pinMap);
+}
+
+void loraLoop()
+{
+    LoRaWAN.loop();
+}
+
+int loraSend(CayenneLPP payload)
+{
+    Serial.println("Sending payload...");
+    if (LMIC.opmode & OP_TXRXPEND)
+    {
+        Serial.println(F("TX/RX operation still pending, not sending payload."));
+        return STATE_READY;
+    }
+    else
+    {
+        LMIC_setTxData2(1, payload.getBuffer(), payload.getSize(), 0);
+        Serial.println(F("Payload queued for sending."));
+    }
+    return STATE_SENDING;
+}
+
+void onEvent(ev_t ev)
+{
+    Serial.printf("%d: ", os_getTime());
+    switch (ev)
+    {
+    case EV_SCAN_TIMEOUT:
+        Serial.println(F("EV_SCAN_TIMEOUT"));
+        break;
+    case EV_BEACON_FOUND:
+        Serial.println(F("EV_BEACON_FOUND"));
+        break;
+    case EV_BEACON_MISSED:
+        Serial.println(F("EV_BEACON_MISSED"));
+        break;
+    case EV_BEACON_TRACKED:
+        Serial.println(F("EV_BEACON_TRACKED"));
+        break;
+    case EV_JOINING:
+        Serial.println(F("EV_JOINING"));
+        break;
+    case EV_JOINED:
+        Serial.println(F("EV_JOINED"));
+        powerLed(AXP20X_LED_LOW_LEVEL);
+        break;
+    case EV_RFU1:
+        Serial.println(F("EV_RFU1"));
+        break;
+    case EV_JOIN_FAILED:
+        Serial.println(F("EV_JOIN_FAILED"));
+        break;
+    case EV_REJOIN_FAILED:
+        Serial.println(F("EV_REJOIN_FAILED"));
+        break;
+        break;
+    case EV_TXCOMPLETE:
+        Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
+        if (LMIC.txrxFlags & TXRX_ACK)
+            Serial.println(F("Received ack"));
+        if (LMIC.dataLen)
+        {
+            Serial.println(F("Received "));
+            Serial.println(LMIC.dataLen);
+            Serial.println(F(" bytes of payload"));
+        }
+        // go to sleep
+        state = STATE_DONE;
+        break;
+    case EV_LOST_TSYNC:
+        Serial.println(F("EV_LOST_TSYNC"));
+        break;
+    case EV_RESET:
+        Serial.println(F("EV_RESET"));
+        break;
+    case EV_RXCOMPLETE:
+        Serial.println(F("EV_RXCOMPLETE"));
+        break;
+    case EV_LINK_DEAD:
+        Serial.println(F("EV_LINK_DEAD"));
+        break;
+    case EV_LINK_ALIVE:
+        Serial.println(F("EV_LINK_ALIVE"));
+        break;
+    case EV_SCAN_FOUND:
+        Serial.println(F("EV_SCAN_FOUND"));
+        break;
+    case EV_TXSTART:
+        Serial.println(F("EV_TXSTART"));
+        break;
+    case EV_TXCANCELED:
+        Serial.println(F("EV_TXCANCELLED"));
+        break;
+    case EV_JOIN_TXCOMPLETE:
+        Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
+        // restart sending
+        state = STATE_READY;
+        break;
+    default:
+        Serial.println(F("Unknown event " + (unsigned)ev));
+        break;
+    }
+}
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index f84574a..c4db76f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,315 +1,42 @@
-#include <config.h>
-#include <axp20x.h>
+#include "config.h"
+#include "states.h"
 #include <Arduino_LoRaWAN_ttn.h>
 #include <lmic.h>
 #include <hal/hal.h>
 #include <SPI.h>
-#include <TinyGPS++.h>
 #include <CayenneLPP.h>
-#include <OneButton.h>
+#include "power.h"
+#include "gps.h"
+#include "lora.h"
+#include "button.h"
 
-#define STATE_WAIT 1
-#define STATE_READY 2
-#define STATE_SENDING 3
-#define STATE_DONE 4
-
-class cLoRaWAN : public Arduino_LoRaWAN_ttn
-{
-public:
-    cLoRaWAN(){};
-
-protected:
-    virtual bool GetOtaaProvisioningInfo(Arduino_LoRaWAN::OtaaProvisioningInfo *) override;
-    virtual void NetSaveFCntUp(uint32_t uFCntUp) override;
-    virtual void NetSaveFCntDown(uint32_t uFCntDown) override;
-    virtual void NetSaveSessionInfo(const SessionInfo &Info, const uint8_t *pExtraInfo, size_t nExtraInfo) override;
-};
-
-bool cLoRaWAN::GetOtaaProvisioningInfo(
-    OtaaProvisioningInfo *pInfo)
-{
-    static const uint8_t DEVEUI[8] = TTN_DEVEUI;
-    static const uint8_t APPEUI[8] = TTN_APPEUI;
-    static const uint8_t APPKEY[16] = TTN_APPKEY;
-
-    if (pInfo)
-    {
-        memcpy(pInfo->AppKey, APPKEY, sizeof(APPKEY));
-        memcpy(pInfo->DevEUI, DEVEUI, sizeof(DEVEUI));
-        memcpy(pInfo->AppEUI, APPEUI, sizeof(APPEUI));
-    }
-    return true;
-}
-
-void cLoRaWAN::NetSaveFCntDown(uint32_t uFCntDown)
-{
-}
-
-void cLoRaWAN::NetSaveFCntUp(uint32_t uFCntUp)
-{
-}
-
-void cLoRaWAN::NetSaveSessionInfo(
-    const SessionInfo &Info,
-    const uint8_t *pExtraInfo,
-    size_t nExtraInfo)
-{
-}
-
-const cLoRaWAN::lmic_pinmap pinMap = {
-    .nss = 18,
-    .rxtx = cLoRaWAN::lmic_pinmap::LMIC_UNUSED_PIN,
-    .rst = 23,
-    .dio = {26, 33, 32},
-};
-
-AXP20X_Class axp;
-HardwareSerial GPS(1);
-TinyGPSPlus gps;
-cLoRaWAN LoRaWAN{};
-OneButton btn = OneButton(GPIO_NUM_38, true, true);
-CayenneLPP lpp(55);
-DynamicJsonDocument jsonBuffer(1024);
 int state;
-unsigned long timerGpsFix;
 RTC_DATA_ATTR int bootCount = 0;
+CayenneLPP payload(55);
 
-void onEvent(ev_t ev)
-{
-    Serial.printf("%d: ", os_getTime());
-    switch (ev)
-    {
-    case EV_SCAN_TIMEOUT:
-        Serial.println(F("EV_SCAN_TIMEOUT"));
-        break;
-    case EV_BEACON_FOUND:
-        Serial.println(F("EV_BEACON_FOUND"));
-        break;
-    case EV_BEACON_MISSED:
-        Serial.println(F("EV_BEACON_MISSED"));
-        break;
-    case EV_BEACON_TRACKED:
-        Serial.println(F("EV_BEACON_TRACKED"));
-        break;
-    case EV_JOINING:
-        Serial.println(F("EV_JOINING"));
-        break;
-    case EV_JOINED:
-        Serial.println(F("EV_JOINED"));
-        axp.setChgLEDMode(AXP20X_LED_LOW_LEVEL);
-        break;
-    case EV_RFU1:
-        Serial.println(F("EV_RFU1"));
-        break;
-    case EV_JOIN_FAILED:
-        Serial.println(F("EV_JOIN_FAILED"));
-        break;
-    case EV_REJOIN_FAILED:
-        Serial.println(F("EV_REJOIN_FAILED"));
-        break;
-        break;
-    case EV_TXCOMPLETE:
-        Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
-        if (LMIC.txrxFlags & TXRX_ACK)
-            Serial.println(F("Received ack"));
-        if (LMIC.dataLen)
-        {
-            Serial.println(F("Received "));
-            Serial.println(LMIC.dataLen);
-            Serial.println(F(" bytes of payload"));
-        }
-        // go to sleep
-        state = STATE_DONE;
-        break;
-    case EV_LOST_TSYNC:
-        Serial.println(F("EV_LOST_TSYNC"));
-        break;
-    case EV_RESET:
-        Serial.println(F("EV_RESET"));
-        break;
-    case EV_RXCOMPLETE:
-        Serial.println(F("EV_RXCOMPLETE"));
-        break;
-    case EV_LINK_DEAD:
-        Serial.println(F("EV_LINK_DEAD"));
-        break;
-    case EV_LINK_ALIVE:
-        Serial.println(F("EV_LINK_ALIVE"));
-        break;
-    case EV_SCAN_FOUND:
-        Serial.println(F("EV_SCAN_FOUND"));
-        break;
-    case EV_TXSTART:
-        Serial.println(F("EV_TXSTART"));
-        break;
-    case EV_TXCANCELED:
-        Serial.println(F("EV_TXCANCELLED"));
-        break;
-    case EV_JOIN_TXCOMPLETE:
-        Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
-        // restart sending
-        state = STATE_READY;
-        break;
-    default:
-        Serial.println(F("Unknown event " + (unsigned)ev));
-        break;
-    }
-}
-
-void printPowerStatus()
-{
-    Serial.println("##############################");
-    Serial.println("--- Power ---");
-    Serial.printf("DCDC1/OLED Status   : %s\n", axp.isDCDC1Enable() ? "enabled" : "disabled");
-    Serial.printf("DCDC1/OLED Voltage  : %g V\n", (float)axp.getDCDC1Voltage() / 1000);
-    Serial.printf("DCDC2/N/C Status    : %s\n", axp.isDCDC2Enable() ? "enabled" : "disabled");
-    Serial.printf("DCDC2/N/C Voltage   : %g V\n", (float)axp.getDCDC2Voltage() / 1000);
-    Serial.printf("DCDC3/ESP32 Status  : %s\n", axp.isDCDC3Enable() ? "enabled" : "disabled");
-    Serial.printf("DCDC3/ESP32 Voltage : %g V\n", (float)axp.getDCDC3Voltage() / 1000);
-    Serial.printf("LDO2/LoRa Status    : %s\n", axp.isLDO2Enable() ? "enabled" : "disabled");
-    Serial.printf("LDO2/LoRa Voltage   : %g V\n", (float)axp.getLDO2Voltage() / 1000);
-    Serial.printf("LDO3/GPS Status     : %s\n", axp.isLDO3Enable() ? "enabled" : "disabled");
-    Serial.printf("LDO3/GPS Voltage    : %g V\n", (float)axp.getLDO3Voltage() / 1000);
-    Serial.println("--- Battery ---");
-    Serial.printf("Battery Connected: %s\n", axp.isBatteryConnect() ? "true" : "false");
-    if (axp.isBatteryConnect())
-    {
-        Serial.printf("Battery Percentage : %d %%\n", axp.getBattPercentage());
-        Serial.printf("Battery Voltage    : %g V\n", axp.getBattVoltage() / 1000);
-        Serial.printf("Battery Current    : %g mA\n", axp.getBattDischargeCurrent());
-        Serial.println("--- Charging ---");
-        Serial.printf("Charging Enabled   : %s\n", axp.isChargeingEnable() ? "true" : "false");
-        Serial.printf("Battery Charging   : %s\n", axp.isChargeing() ? "true" : "false");
-        Serial.printf("Set Charge Current : %g mA\n", axp.getSettingChargeCurrent());
-    }
-    Serial.println("--- Chip ---");
-    Serial.printf("Chip Temp : %.1f C°\n", axp.getTemp() / 10);
-    Serial.println("##############################");
-}
-
-void printGpsStatus()
-{
-    while (GPS.available())
-    {
-        gps.encode(GPS.read());
-    }
-    Serial.println("##############################");
-    Serial.println("--- GPS ---");
-    Serial.printf("Latitude   : %f\n", gps.location.lat());
-    Serial.printf("Longitude  : %f\n", gps.location.lng());
-    Serial.printf("Satellites : %d\n", gps.satellites.value());
-    Serial.printf("Altitude   : %g m\n", gps.altitude.meters());
-    Serial.printf("Time       : %02d:%02d:%02d\n", gps.time.hour(), gps.time.minute(), gps.time.second());
-    Serial.printf("Speed      : %g\n", gps.speed.kmph());
-    Serial.println("##############################");
-}
-
-void setupPower()
-{
-    if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS))
-    {
-        Serial.println("AXP192 Begin PASS");
-    }
-    else
-    {
-        Serial.println("AXP192 Begin FAIL");
-    }
-    axp.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); // OLED  : off
-    axp.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // N/C   : off
-    axp.setPowerOutPut(AXP192_DCDC3, AXP202_ON);  // ESP32 : on
-    axp.setPowerOutPut(AXP192_LDO2, AXP202_ON);   // LORA  : on
-    axp.setPowerOutPut(AXP192_LDO3, AXP202_ON);   // GPS   : on
-    /*
-    axp.setDCDC3Voltage(3300);                    // ESP32 : 3.3V
-    axp.setLDO2Voltage(3300);                     // LORA  : 3.3V
-    axp.setLDO3Voltage(3300);                     // GPS   : 3.3V
-    */
-    axp.setChgLEDMode(AXP20X_LED_OFF);
-    printPowerStatus();
-}
-
-void setupGps()
-{
-    GPS.begin(9600, SERIAL_8N1, 34, 12); // IO34: RX, IO12: TX
-}
-
-void checkGpsFix()
+void createPayload()
 {
-    if (millis() - timerGpsFix >= 1000 * 1) // 1 second
-    {
-        Serial.println("Waiting for GPS fix...");
-        while (GPS.available())
-        {
-            gps.encode(GPS.read());
-        }
-        if (gps.location.isValid())
-        {
-            printGpsStatus();
-            state = STATE_READY;
-        }
-        timerGpsFix = millis();
-    }
+    payload.reset();
+    TinyGPSPlus gps = getGps();
+    payload.addGPS(1, gps.location.lat(), gps.location.lng(), gps.altitude.meters());
+    payload.addAnalogInput(2, powerGetBattVoltage() / 1000);
 }
 
-void createPayload()
-{
-    while (GPS.available())
-    {
-        gps.encode(GPS.read());
-    }
-    lpp.reset();
-    lpp.addGPS(1, gps.location.lat(), gps.location.lng(), gps.altitude.meters());
-    lpp.addAnalogInput(2, axp.getBattVoltage() / 1000);
-    // debug output
+void printPayload() {
+    DynamicJsonDocument jsonBuffer(1024);
     JsonObject json = jsonBuffer.to<JsonObject>();
-    lpp.decodeTTN(lpp.getBuffer(), lpp.getSize(), json);
+    payload.decodeTTN(payload.getBuffer(), payload.getSize(), json);
+    Serial.println("\n########## Payload ##########");
     serializeJsonPretty(json, Serial);
-    Serial.println();
-}
-
-void sendPayload()
-{
-    if (LMIC.opmode & OP_TXRXPEND)
-    {
-        Serial.println(F("OP_TXRXPEND, not sending"));
-        state = STATE_READY;
-    }
-    else
-    {
-        LMIC_setTxData2(1, lpp.getBuffer(), lpp.getSize(), 0);
-        Serial.println(F("Packet queued"));
-    }
+    Serial.println("\n#########################\n");
 }
 
-void sendStatus()
+static void bootInfo()
 {
-    Serial.println("Sending status...");
-    axp.setChgLEDMode(AXP20X_LED_BLINK_4HZ);
-    createPayload();
-    sendPayload();
-    state = STATE_SENDING;
-}
-
-void doSleep()
-{
-    Serial.println("Going to sleep for " + String(TIME_SLEEP) + " seconds.");
-    axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA : off
-    axp.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // GPS  : off
-    axp.setChgLEDMode(AXP20X_LED_OFF);
-    esp_sleep_enable_ext0_wakeup(GPIO_NUM_38, LOW); // wake up with "user" button (middle)
-    esp_sleep_enable_timer_wakeup(1000000 * TIME_SLEEP);
-    esp_deep_sleep_start();
-}
-
-static void handleClick()
-{
-    doSleep();
-}
+    ++bootCount;
+    Serial.println("Boot number: " + String(bootCount));
 
-void print_wakeup_reason()
-{
     esp_sleep_wakeup_cause_t wakeup_reason;
-
     wakeup_reason = esp_sleep_get_wakeup_cause();
 
     switch (wakeup_reason)
@@ -339,36 +66,34 @@ void setup()
 {
     Serial.begin(115200);
     Wire.begin(21, 22);
-    ++bootCount;
-    Serial.println("Boot number: " + String(bootCount));
-    print_wakeup_reason();
-    setupPower();
-    setupGps();
-    LoRaWAN.begin(pinMap);
-    btn.attachClick(handleClick);
-    timerGpsFix = millis();
-    axp.setChgLEDMode(AXP20X_LED_BLINK_1HZ);
-    state = STATE_WAIT;
+    bootInfo();
+    powerSetup();
+    gpsSetup();
+    loraSetup();
+    buttonSetup();
+    state = STATE_INIT;
 }
 
 void loop()
 {
-    btn.tick();
-    LoRaWAN.loop();
+    loraLoop();
+    buttonLoop();
 
     switch (state)
     {
-    case STATE_WAIT:
-        checkGpsFix();
+    case STATE_INIT:
+        gpsCheck();
         break;
     case STATE_READY:
-        sendStatus();
+        createPayload();
+        state = loraSend(payload); // success: STATE_SENDING, error: STATE_READY
+        printPayload();
         break;
     case STATE_SENDING:
         // idle
         break;
     case STATE_DONE:
-        doSleep();
+        powerSleep();
         break;
     default:
         break;
diff --git a/src/power.cpp b/src/power.cpp
new file mode 100644
index 0000000..89e276f
--- /dev/null
+++ b/src/power.cpp
@@ -0,0 +1,86 @@
+#include "config.h"
+#include <axp20x.h>
+
+AXP20X_Class axp;
+
+void powerLed(axp_chgled_mode_t mode)
+{
+    axp.setChgLEDMode(mode);
+}
+
+float powerGetBattVoltage()
+{
+    return axp.getBattVoltage();
+}
+
+void powerSetOutput(uint8_t channel, bool enabled)
+{
+    axp.setPowerOutPut(channel, enabled);
+}
+
+void powerStatus()
+{
+    Serial.println("\n########## Power ##########");
+    Serial.printf("DCDC1/OLED Status   : %s\n", axp.isDCDC1Enable() ? "enabled" : "disabled");
+    Serial.printf("DCDC1/OLED Voltage  : %g V\n", (float)axp.getDCDC1Voltage() / 1000);
+    Serial.printf("DCDC2/N/C Status    : %s\n", axp.isDCDC2Enable() ? "enabled" : "disabled");
+    Serial.printf("DCDC2/N/C Voltage   : %g V\n", (float)axp.getDCDC2Voltage() / 1000);
+    Serial.printf("DCDC3/ESP32 Status  : %s\n", axp.isDCDC3Enable() ? "enabled" : "disabled");
+    Serial.printf("DCDC3/ESP32 Voltage : %g V\n", (float)axp.getDCDC3Voltage() / 1000);
+    Serial.printf("LDO2/LoRa Status    : %s\n", axp.isLDO2Enable() ? "enabled" : "disabled");
+    Serial.printf("LDO2/LoRa Voltage   : %g V\n", (float)axp.getLDO2Voltage() / 1000);
+    Serial.printf("LDO3/GPS Status     : %s\n", axp.isLDO3Enable() ? "enabled" : "disabled");
+    Serial.printf("LDO3/GPS Voltage    : %g V\n", (float)axp.getLDO3Voltage() / 1000);
+    Serial.println("--- Battery ---");
+    Serial.printf("Battery Connected: %s\n", axp.isBatteryConnect() ? "true" : "false");
+    if (axp.isBatteryConnect())
+    {
+        Serial.printf("Battery Percentage : %d %%\n", axp.getBattPercentage());
+        Serial.printf("Battery Voltage    : %g V\n", axp.getBattVoltage() / 1000);
+        Serial.printf("Battery Current    : %g mA\n", axp.getBattDischargeCurrent());
+        Serial.println("--- Charging ---");
+        Serial.printf("Charging Enabled   : %s\n", axp.isChargeingEnable() ? "true" : "false");
+        Serial.printf("Battery Charging   : %s\n", axp.isChargeing() ? "true" : "false");
+        Serial.printf("Set Charge Current : %g mA\n", axp.getSettingChargeCurrent());
+    }
+    Serial.println("--- Chip ---");
+    Serial.printf("Chip Temp : %.1f C°\n", axp.getTemp() / 10);
+    Serial.println("#########################\n");
+}
+
+void powerSetup()
+{
+    if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS))
+    {
+        Serial.println("AXP192 Begin PASS");
+    }
+    else
+    {
+        Serial.println("AXP192 Begin FAIL");
+    }
+    axp.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); // OLED  : off
+    axp.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // N/C   : off
+    axp.setPowerOutPut(AXP192_DCDC3, AXP202_ON);  // ESP32 : on
+    axp.setPowerOutPut(AXP192_LDO2, AXP202_ON);   // LORA  : on
+    axp.setPowerOutPut(AXP192_LDO3, AXP202_ON);   // GPS   : on
+    /*
+    axp.setDCDC3Voltage(3300);                    // ESP32 : 3.3V
+    axp.setLDO2Voltage(3300);                     // LORA  : 3.3V
+    axp.setLDO3Voltage(3300);                     // GPS   : 3.3V
+    */
+    axp.setChgLEDMode(AXP20X_LED_OFF);
+    powerStatus();
+}
+
+void powerSleep()
+{
+    Serial.printf("\nSystem has been up for %llu seconds.\n", esp_timer_get_time()/1000000);
+    Serial.printf("Going to sleep for %d seconds.\n", SLEEP_SECONDS);
+    powerSetOutput(AXP192_LDO2, AXP202_OFF); // LORA : off
+    powerSetOutput(AXP192_LDO3, AXP202_OFF); // GPS  : off
+    powerLed(AXP20X_LED_OFF);
+    esp_sleep_enable_ext0_wakeup(GPIO_NUM_38, LOW); // wake up with "user" button (middle)
+    esp_sleep_enable_timer_wakeup(1000000 * SLEEP_SECONDS);
+    Serial.println("\n### END ###");
+    esp_deep_sleep_start();
+}
\ No newline at end of file
-- 
GitLab