OPEnSLab-OSU / Loom-V4

Open Source Internet Of Things Rapid Prototyping Framework For Environmental Sensing Applications
5 stars 1 forks source link

LoRa Batch first packet sent/received is null packet #126

Closed nicholasqle closed 3 months ago

nicholasqle commented 5 months ago

Describe the bug Every time the LoRa Batch transmission is done, the first packet is always null. The rest of the packets are normal.

Hardware in Use Transmitting Side: Feather M0 LoRa, Dendrometer PCB, AS5311 Receiving Side: Feather M0 LoRa, Weatherchimes PCB, SparkFun LTE CAT M1/NB-IoT Shield

To Reproduce Steps to reproduce the behavior:

  1. The batch size for LoRa transmission is reached on the transmitting device
  2. Observe the serial monitor on the receiving device to see the displayed packets.

Expected behavior The first packet should contain all of the measurements

Code In the Arduino IDE while editing the main.ino, goto 'Edit' -> 'Copy for HTML' and paste the output of that here Transmitting side (Dendrometer):

#include <Loom_Manager.h> //4.6
#include <Hardware/Loom_Hypnos/Loom_Hypnos.h>
#include <Hardware/Actuators/Loom_Neopixel/Loom_Neopixel.h>
#include <Sensors/Loom_Analog/Loom_Analog.h>
#include <Sensors/Analog/Loom_Teros10/Loom_Teros10.h>
#include <Sensors/I2C/Loom_SHT31/Loom_SHT31.h>
#include <Radio/Loom_LoRa/Loom_LoRa.h>
#include <Internet/Connectivity/Loom_Wifi/Loom_Wifi.h>
#include <Internet/Logging/Loom_MongoDB/Loom_MongoDB.h>

#include "AS5311.h"

//////////////////////////
/* DEVICE CONFIGURATION */
//////////////////////////
static const uint8_t NODE_NUMBER = 1;
static const char * DEVICE_NAME = "BlueberryDendrometer_";
////Select one wireless communication option
#define DENDROMETER_LORA
// #define DENDROMETER_WIFI
////These two time values are added together to determine the measurement interval
static const int8_t MEASUREMENT_INTERVAL_MINUTES = 1;
static const int8_t MEASUREMENT_INTERVAL_SECONDS = 0;
static const uint8_t TRANSMIT_INTERVAL = 16; // to save power, only transmit every X measurements
////Use teros 10?
#define DENDROMETER_TEROS10
//////////////////////////
//////////////////////////

// Pins
#define AS5311_CS A3 // 9 for LB version, A3 otherwise
#define AS5311_CLK A5
#define AS5311_DO A4
#define BUTTON_PIN A1 // 11 for LB, A1 otherwise

// Loom
Manager manager(DEVICE_NAME, NODE_NUMBER);
Loom_Hypnos hypnos(manager, HYPNOS_VERSION::V3_3, TIME_ZONE::PST); // 3_2 for LB, 3_3 otherwise
// Loom Sensors
Loom_Analog analog(manager);
#ifdef DENDROMETER_TEROS10
Loom_Teros10 teros(manager, A0);
#endif
Loom_SHT31 sht(manager);
Loom_Neopixel statusLight(manager, false, false, true, NEO_GRB); // using channel 2 (physical pin A2). use RGB for through-hole LED devices. GRB otherwise.

// magnet sensor
AS5311 magnetSensor(AS5311_CS, AS5311_CLK, AS5311_DO);

// wireless
#if defined DENDROMETER_LORA && defined DENDROMETER_WIFI
#error Choose ONE wireless communication protocol.
#elif defined DENDROMETER_LORA
Loom_LoRa lora(manager, NODE_NUMBER);
Loom_BatchSD batchSD(hypnos, TRANSMIT_INTERVAL);
#elif defined DENDROMETER_WIFI
#include "credentials/arduino_secrets.h"
Loom_WIFI wifi(manager, CommunicationMode::CLIENT, SECRET_SSID, SECRET_PASS);
Loom_MongoDB mqtt(manager, wifi.getClient(), SECRET_BROKER, SECRET_PORT, MQTT_DATABASE, BROKER_USER, BROKER_PASS);
Loom_BatchSD batchSD(hypnos, TRANSMIT_INTERVAL);
#else
#warning Wireless communication disabled!
#endif

// Global Variables
volatile bool buttonPressed = false; // Check to see if button was pressed

void sleepCycle();
void ISR_RTC();
void ISR_BUTTON();

void measure();
void measureVPD();
void transmit();

void setRTC(bool);
void checkMagnetSensor();
void alignMagnetSensor();
bool checkStableAlignment();
void displayMagnetStatus(magnetStatus);
void flashColor(uint8_t r, uint8_t g, uint8_t b);

/**
 * Program setup
 */
void setup()
{
    pinMode(BUTTON_PIN, INPUT_PULLUP); // Enable pullup on button pin. this is necessary for the interrupt (and the button check on the next line)
    delay(10);
    bool userInput = !digitalRead(BUTTON_PIN); // wait for serial connection ONLY button is pressed (low reading)
    manager.beginSerial(userInput);            // wait for serial connection ONLY button is pressed

    hypnos.setLogName("BlueberryDendrometer_1data"); //SD card CSV file name

    hypnos.enable();
    manager.initialize();
#if defined DENDROMETER_WIFI
    wifi.setBatchSD(batchSD);
    wifi.setMaxRetries(2);
    mqtt.setMaxRetries(1);
    wifi.loadConfigFromJSON(hypnos.readFile("wifi_creds.json"));
    mqtt.loadConfigFromJSON(hypnos.readFile("mqtt_creds.json"));
    hypnos.setNetworkInterface(&wifi);
    hypnos.networkTimeUpdate();
#elif defined DENDROMETER_LORA
    lora.setBatchSD(batchSD);
#endif

    setRTC(userInput);

    checkMagnetSensor();
    alignMagnetSensor();

    hypnos.registerInterrupt(ISR_RTC);
}

/**
 * Main loop
 */
void loop()
{
    measure();
    if (buttonPressed) // if interrupt button was pressed, display staus of magnet sensor
    {
        displayMagnetStatus(magnetSensor.getMagnetStatus());
    #if defined DENDROMETER_WIFI
        hypnos.networkTimeUpdate();
    #endif
        delay(3000);
        statusLight.set_color(2, 0, 0, 0, 0); // LED Off
        buttonPressed = false;
    }
    transmit();
    sleepCycle();
}

/**
 * Perform all measurements for the dendrometer and put them into a packet.
 * Log to SD card.
 */
void measure()
{
    manager.measure();
    manager.package();

    measureVPD();
    magnetSensor.measure(manager);
    // Log whether system woke up from button or not
    manager.addData("Button", "Pressed?", buttonPressed);

    hypnos.logToSD();

    // delay(5000);
    // manager.display_data();
}

/**
 * Log readings from the SHT31 sensor. Also calculate and log VPD.
 */
void measureVPD()
{
    float SVP, VPD, temperature, humidity;
    float e = 2.71828;

    temperature = sht.getTemperature();
    humidity = sht.getHumidity();

    // Tetens equation
    SVP = (0.61078 * pow(e, (17.2694 * temperature) / (temperature + 237.3)));
    VPD = SVP * (1 - (humidity / 100));

    manager.addData("SHT31", "VPD", VPD);
}

/**
 * transmit the batch data packet over LoRa
 */
void transmit()
{
#if defined DENDROMETER_LORA
    lora.sendBatch(0);
#elif defined DENDROMETER_WIFI
    if (!batchSD.shouldPublish())
    {
        char output[100];
        snprintf_P(output, OUTPUT_SIZE, PSTR("<Dendrometer> Not ready to publish. Currently at packet %i of %i"),
                   batchSD.getCurrentBatch(), batchSD.getBatchSize());
        Serial.println(output);
    }
    if (wifi.isConnected())
        mqtt.publish(batchSD);
#endif
}

/**
 * Shut down the device for a specified time period to save power.
 */
void sleepCycle()
{
    hypnos.setInterruptDuration(TimeSpan(0, 0, MEASUREMENT_INTERVAL_MINUTES, MEASUREMENT_INTERVAL_SECONDS));
    // Reattach to the interrupt after we have set the alarm so we can have repeat triggers
    hypnos.reattachRTCInterrupt();
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), ISR_BUTTON, FALLING);

    // Put the device into a deep sleep, operation HALTS here until the interrupt is triggered
    hypnos.sleep();
    detachInterrupt(digitalPinToInterrupt(BUTTON_PIN));
}

// Interrupt routines
void ISR_RTC()
{
    hypnos.wakeup();
}

void ISR_BUTTON()
{
    buttonPressed = true;
    hypnos.wakeup();
}

/**
 * Magnet alignment procedure. Displays magnet sensor to user until
 * the magnet is determined to be properly aligned and maintains that alignment
 * for a certain amount of time
 */
void alignMagnetSensor()
{
    magnetStatus status;

    Serial.println(F("<Dendrometer> Waiting for magnet alignment"));
    while (1)
    {
        // Watchdog.reset();
        status = magnetSensor.getMagnetStatus();
        displayMagnetStatus(status);
        delay(100);
        if (status == magnetStatus::green && checkStableAlignment())
            break;
    }
    flashColor(0, 255, 0);
}

/**
 * Check the magnet sensor alignment status and display it on the multi-color LED
 * @param   status  the magnetStatus to display
 */
void displayMagnetStatus(magnetStatus status)
{
    switch (status)
    {
    case magnetStatus::yellow:
        statusLight.set_color(2, 0, 255, 100, 0); // yellow
        break;
    case magnetStatus::green:
        statusLight.set_color(2, 0, 0, 255, 0); // green
        break;
    case magnetStatus::error: // Fall through
    case magnetStatus::red:   // Fall through
    default:
        statusLight.set_color(2, 0, 255, 0, 0); // red
        break;                                  // do nothing
    }
}

/**
 * Flashes status light
 * @param   r   red color value (unsigned 8 bit number)
 * @param   g   green color value (unsigned 8 bit number)
 * @param   b   blue color value (unsigned 8 bit number)
 */
void flashColor(uint8_t r, uint8_t g, uint8_t b)
{
    for (auto _ = 6; _--;)
    {
        // Watchdog.reset();
        statusLight.set_color(2, 0, r, g, b);
        delay(250);
        statusLight.set_color(2, 0, 0, 0, 0); // off
        delay(250);
    }
}

/**
 * Make sure calibration is stable before proceeding
 * Returns true if the sensor remains aligned for the next five seconds
 */
bool checkStableAlignment()
{
    const unsigned int CHECK_TIME = 3000;
    magnetStatus status;
    bool aligned = true;

    for (int i = 0; i < (CHECK_TIME / 100); i++)
    {
        // Watchdog.reset();
        status = magnetSensor.getMagnetStatus();
        if (status != magnetStatus::green)
        {
            aligned = false;
            break;
        }
        delay(100);
    }

    return aligned;
}

/**
 * Checks to see if a magnet sensor is connected and functioning.
 */
void checkMagnetSensor()
{
    uint32_t data = magnetSensor.getRawData();
    if (__builtin_parity(data) == 0 && data != 0) //__builtin_parity() returns 0 if value has even parity
        return;
    for (auto _ = 6; _--;)
        flashColor(255, 100, 0); // if the check didn't pass, alert the user by flashing the LED
}

/**
 * Ask the user to set a custom time
 */
void setRTC(bool wait)
{
    if (!wait || !Serial)
        return;

    Serial.println(F("<Dendrometer> Adjust RTC time? (y/n)"));
    while (!Serial.available())
        ;
    int val = Serial.read();
    delay(50);
    while (Serial.available())
        Serial.read(); // flush the input buffer to avoid invalid input to rtc function

    if (val == 'y')
    {
        hypnos.set_custom_time();
    }
}

Receiving Side (Hub):

#include "arduino_secrets.h"

#include <Loom_Manager.h> //4.6

#include <Hardware/Loom_Hypnos/Loom_Hypnos.h>
#include <Radio/Loom_LoRa/Loom_LoRa.h>
#include <Sensors/Loom_Analog/Loom_Analog.h>
#include <Internet/Connectivity/Loom_LTE/Loom_LTE.h>
#include <Internet/Logging/Loom_MongoDB/Loom_MongoDB.h>

const unsigned long REPORT_INTERVAL = 1 * 60 * 60 * 1000;

Manager manager("Hub", 0);

Loom_Hypnos hypnos(manager, HYPNOS_VERSION::V3_3, TIME_ZONE::PST);
Loom_Analog batteryVoltage(manager);
Loom_LoRa lora(manager);
Loom_LTE lte(manager, "hologram", "", "", A5);
Loom_MongoDB mqtt(manager, lte.getClient(), SECRET_BROKER, SECRET_PORT, DATABASE, BROKER_USER, BROKER_PASS);
Loom_BatchSD batchSD(hypnos, 16); //set batch size uploading

void setup()
{
    // Start the serial interface
    manager.beginSerial();

    // Enable the power rails on the hypnos
    hypnos.enable();

    setRTC();

    // Sets the LTE board to use batch SD to only start when we actually need to publish data
    lte.setBatchSD(batchSD);

    // load MQTT credentials from the SD card, if they exist
    mqtt.loadConfigFromJSON(hypnos.readFile("mqtt_creds.json"));

    // Initialize the modules
    manager.initialize();
}

void loop()
{
    // Wait 5 seconds for a message
    if (lora.receiveBatch(5000, 0))
    {
        manager.display_data();
        hypnos.logToSD();
        mqtt.publish(batchSD);
    }
    static unsigned long timer = millis();
    if (millis() - timer > REPORT_INTERVAL)
    {
        manager.set_device_name("Hub");
        manager.set_instance_num(0);

        manager.measure();
        manager.package();
        manager.display_data();
        mqtt.publish();
        //restart LTE if connection to internet fails
        if (!lte.verifyConnection())
        {
            lte.power_down();
            delay(5000);
            lte.power_up();
        }

        timer = millis();
    }
}

void setRTC()
{
    if (!Serial)
        return;

    Serial.println(F("Adjust RTC time? (y/n)"));
    unsigned long timer = millis();
    while (!Serial.available() && (millis() - timer) < 7000)
        ;
    if (!Serial.available())
        return;
    int val = Serial.read();
    delay(50);
    while (Serial.available())
        Serial.read(); // flush the input buffer to avoid invalid input to rtc function

    if (val == 'y')
    {
        hypnos.set_custom_time();
    }
}

Output Copy and paste the serial output here if possible wrapped in ``` ```

[2024.01.28 10:42:48] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:50] [DEBUG] [Loom_LoRa.cpp:receiveBatch:312] Packet Received!
[2024.01.28 10:42:50] [DEBUG] [Loom_LoRa.cpp:receiveBatch:337] Received packet!
[2024.01.28 10:42:50] [DEBUG] [Loom_Manager.cpp:display_data:198] Data Json: 

{
  "type": null,
  "id": {
    "name": null,
    "instance": 0
  },
  "contents": []
}
[2024.01.28 10:42:50] [DEBUG] [SDManager.cpp:log:159] Successfully logged data to Hub0.csv
[2024.01.28 10:42:50] [DEBUG] [Loom_MongoDB.cpp:publish:148] Batch not ready to publish: 1/16
[2024.01.28 10:42:50] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:52] [DEBUG] [Loom_LoRa.cpp:receiveBatch:312] Packet Received!
[2024.01.28 10:42:52] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 1 / 8
[2024.01.28 10:42:52] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:52] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 1 / 8
[2024.01.28 10:42:52] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 2 / 8
[2024.01.28 10:42:52] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:53] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 2 / 8
[2024.01.28 10:42:53] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 3 / 8
[2024.01.28 10:42:53] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:54] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 3 / 8
[2024.01.28 10:42:54] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 4 / 8
[2024.01.28 10:42:54] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:55] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 4 / 8
[2024.01.28 10:42:55] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 5 / 8
[2024.01.28 10:42:55] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:56] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 5 / 8
[2024.01.28 10:42:56] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 6 / 8
[2024.01.28 10:42:56] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:57] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 6 / 8
[2024.01.28 10:42:57] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 7 / 8
[2024.01.28 10:42:57] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:58] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 7 / 8
[2024.01.28 10:42:58] [DEBUG] [Loom_LoRa.cpp:receivePartial:369] Waiting for packet 8 / 8
[2024.01.28 10:42:58] [DEBUG] [Loom_LoRa.cpp:recv:146] Waiting for packet...
[2024.01.28 10:42:59] [DEBUG] [Loom_LoRa.cpp:receivePartial:374] Fragment received 8 / 8
[2024.01.28 10:42:59] [DEBUG] [Loom_LoRa.cpp:receiveBatch:337] Received packet!
[2024.01.28 10:42:59] [DEBUG] [Loom_Manager.cpp:display_data:198] Data Json: 

{
  "type": "data",
  "id": {
    "name": "BlueberryDendrometer_",
    "instance": 1
  },
  "contents": [
    {
      "module": "Packet",
      "data": {
        "Number": 1
      }
    },
    {
      "module": "Analog",
      "data": {
        "Vbat": 4.116943359,
        "Vbat_MV": 4113.720703
      }
    },
    {
      "module": "Teros10",
      "data": {
        "Millivolt_Reading": 914.6519775,
        "Dielectric_Permittivity": 1.39895916,
        "Volumetric_Water_Content": -0.12530835
      }
    },
    {
      "module": "SHT31",
      "data": {
        "Temperature": 21.12999916,
        "Humidity": 61.75,
        "VPD": 0.958822846
      }
    },
    {
      "module": "LoRa",
      "data": {
        "RSSI": 0
      }
    },
    {
      "module": "AS5311",
      "data": {
        "Alignment": "Green",
        "mag": 1022,
        "pos_raw": 2111,
        "pos_avg": 2110
     }
    },
    {
      "module": "displacement",
      "data": {
        "um": 0
      }
    },
    {
      "module": "Button",
      "data": {
        "Pressed?": false
      }
    }
  ],
  "timestamp": {
    "time_utc": "2024-01-28T18:27:50Z",
    "time_local": "2024-01-28T10:27:50Z"
  }
}
[2024.01.28 10:42:59] [DEBUG] [SDManager.cpp:log:159] Successfully logged data to Hub0.csv

Additional context Add any other context about the problem here. I am not sure if this is expected behavior but Chet wanted me to post this here for future people

WL-Richards commented 5 months ago

Testing fix on batch-null-packet

WL-Richards commented 4 months ago

This should be fixed on batch-null-packet but has not been merged into main yet

nicholasqle commented 4 months ago

The required change of

else if (!recvDoc["id"].isNull() && recvDoc["id"]["name"].isNull()){
                return false;
            }

has not been committed yet to batch-null-packet

WL-Richards commented 3 months ago

Now it has been pushed