/* mini-beieli-node.ino BeieliScale, see https://mini-beieli.ch Joerg Lehmann, nbit Informatik GmbH */ // HX711: 0 => compile for hx711, 1 => compile for NAU7802 #define HX711 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mini_beieli_node.h" #if (HX711) #include "mini_beieli_node_hx711.h" #else #include "mini_beieli_node_nau7802.h" #endif #ifndef _HELPER_H_ #include "helper.h" #endif using namespace McciCatena; // forwards static void settleDoneCb(osjob_t* pSendJob); static void warmupDoneCb(osjob_t* pSendJob); static void startNewIterationCb(osjob_t* pJob); static void txNotProvisionedCb(osjob_t *pSendJob); static void sleepDoneCb(osjob_t* pSendJob); static Arduino_LoRaWAN::SendBufferCbFn sendBufferDoneCb; static Arduino_LoRaWAN::ReceivePortBufferCbFn receiveMessage; // Additional Commands // forward reference to the command function cCommandStream::CommandFn cmdHello; cCommandStream::CommandFn cmdGetCalibrationSettings; cCommandStream::CommandFn cmdGetSensorReadings; cCommandStream::CommandFn cmdGetScaleA; cCommandStream::CommandFn cmdGetScaleB; cCommandStream::CommandFn cmdCalibrateZeroScaleA; cCommandStream::CommandFn cmdCalibrateZeroScaleB; cCommandStream::CommandFn cmdCalibrateScaleA; cCommandStream::CommandFn cmdCalibrateScaleB; cCommandStream::CommandFn cmdSetDebugLevel; cCommandStream::CommandFn cmdGetDebugLevel; cCommandStream::CommandFn cmdStopIterations; // the individual commmands are put in this table static const cCommandStream::cEntry sMyExtraCommmands[] = { { "hello", cmdHello }, { "get_calibration_settings", cmdGetCalibrationSettings }, { "get_sensor_readings", cmdGetSensorReadings }, { "calibrate_zero_scale_a", cmdCalibrateZeroScaleA }, { "calibrate_zero_scale_b", cmdCalibrateZeroScaleB }, { "calibrate_scale_a", cmdCalibrateScaleA }, { "calibrate_scale_b", cmdCalibrateScaleB }, { "set_debug_level", cmdSetDebugLevel }, { "get_debug_level", cmdGetDebugLevel }, { "stop_iterations", cmdStopIterations }, // other commands go here.... }; /* a top-level structure wraps the above and connects to the system table */ /* it optionally includes a "first word" so you can for sure avoid name clashes */ static cCommandStream::cDispatch sMyExtraCommands_top( sMyExtraCommmands, /* this is the pointer to the table */ sizeof(sMyExtraCommmands), /* this is the size of the table */ "application" /* this is the "first word" for all the commands in this table*/ ); /****************************************************************************\ | | VARIABLES | \****************************************************************************/ byte my_position = 0; // what is our actual measurement, starts with 0 long timer_pos0; // Global Variables LORA_data lora_data; LORA_data_first lora_data_first; CONFIG_data config_data; SENSOR_data last_sensor_reading; long iteration = 0; // what iteration number do we have, starts with 0 long package_counter = 0; // sent package counter bool send_in_progress = false; bool stop_iterations = false; bool next_package_is_init_package = true; uint32_t gRebootMs; // generic timer long t_cur; // the LoRaWAN backhaul. Note that we use the // Catena version so it can provide hardware-specific // information to the base class. // Catena::LoRaWAN gLoRaWAN; // // the LED StatusLed gLed(Catena::PIN_STATUS_LED); // The temperature/humidity sensor Adafruit_BME280 gBME280; // The default initalizer creates an I2C connection bool fBme; SPIClass gSPI2( Catena::PIN_SPI2_MOSI, Catena::PIN_SPI2_MISO, Catena::PIN_SPI2_SCK); // The flash Catena_Mx25v8035f gFlash; bool fFlash; // USB power bool fUsbPower; // have we printed the sleep info? bool g_fPrintedSleeping = false; // the job that's used to synchronize us with the LMIC code static osjob_t iterationJob; static osjob_t sendJob; // the cycle time to use unsigned gTxCycle; // remaining before we reset to default unsigned gTxCycleCount; void setup(void) { gCatena.begin(); setup_platform(); SetupScales(config_data.debug_level); ClearLoraData(); setup_bme280(); setup_flash(); setup_uplink(); } void setup_platform(void) { if (config_data.debug_level > 0) { gCatena.SafePrintf("Setup_platform\n"); } /* add our application-specific commands */ gCatena.addCommands( sMyExtraCommands_top, nullptr ); // read config_data from fram... if (config_data.debug_level > 0) { gCatena.SafePrintf("Reading Calibration Config from FRAM...\n"); } gCatena.getFram()->getField(cFramStorage::kAppConf, (uint8_t *)&config_data, sizeof(config_data)); if (config_data.debug_level > 0) { gCatena.SafePrintf("Setup_platform, this is the configuration\n"); gCatena.SafePrintf("cal_w1_0: %d\n", config_data.cal_w1_0); gCatena.SafePrintf("cal_w2_0: %d\n", config_data.cal_w2_0); gCatena.SafePrintf("cal_w1_factor: %d.%03d\n", (int)config_data.cal_w1_factor, (int)abs(config_data.cal_w1_factor * 1000) % 1000); gCatena.SafePrintf("cal_w2_factor: %d.%03d\n", (int)config_data.cal_w2_factor, (int)abs(config_data.cal_w2_factor * 1000) % 1000); gCatena.SafePrintf("debug_level: %d\n", (int)config_data.debug_level); } #ifdef USBCON // if running unattended, don't wait for USB connect. if (!(gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fUnattended))) { while (!Serial) /* wait for USB attach */ yield(); } #endif gCatena.SafePrintf("\n"); gCatena.SafePrintf("-------------------------------------------------------------------------------\n"); gCatena.SafePrintf("mini-beieli.ch - BeieliScale Version %d.\n", fwVersion); { char sRegion[16]; gCatena.SafePrintf("Target network: %s / %s\n", gLoRaWAN.GetNetworkName(), gLoRaWAN.GetRegionString(sRegion, sizeof(sRegion))); } gCatena.SafePrintf("Enter 'help' for a list of commands.\n"); #ifdef CATENA_CFG_SYSCLK gCatena.SafePrintf("SYSCLK: %d MHz\n", CATENA_CFG_SYSCLK); #endif #ifdef USBCON gCatena.SafePrintf("USB enabled\n"); #else gCatena.SafePrintf("USB disabled\n"); #endif Catena::UniqueID_string_t CpuIDstring; gCatena.SafePrintf( "CPU Unique ID: %s\n", gCatena.GetUniqueIDstring(&CpuIDstring)); gCatena.SafePrintf("--------------------------------------------------------------------------------\n"); gCatena.SafePrintf("\n"); // set up the LED gLed.begin(); gCatena.registerObject(&gLed); gLed.Set(LedPattern::Off); if (config_data.debug_level > 1) { gLed.Set(LedPattern::FastFlash); } // set up LoRaWAN gCatena.SafePrintf("LoRaWAN init: "); if (!gLoRaWAN.begin(&gCatena)) { gCatena.SafePrintf("failed\n"); } else { gCatena.SafePrintf("succeeded\n"); } gLoRaWAN.SetReceiveBufferBufferCb(receiveMessage); setTxCycleTime(CATCFG_T_CYCLE_INITIAL, CATCFG_INTERVAL_COUNT_INITIAL); gCatena.registerObject(&gLoRaWAN); /* find the platform */ const Catena::EUI64_buffer_t* pSysEUI = gCatena.GetSysEUI(); uint32_t flags; const CATENA_PLATFORM* const pPlatform = gCatena.GetPlatform(); if (pPlatform) { gCatena.SafePrintf("EUI64: "); for (unsigned i = 0; i < sizeof(pSysEUI->b); ++i) { gCatena.SafePrintf("%s%02x", i == 0 ? "" : "-", pSysEUI->b[i]); } gCatena.SafePrintf("\n"); flags = gCatena.GetPlatformFlags(); gCatena.SafePrintf( "Platform Flags: %#010x\n", flags); gCatena.SafePrintf( "Operating Flags: %#010x\n", gCatena.GetOperatingFlags()); } else { gCatena.SafePrintf("**** no platform, check provisioning ****\n"); flags = 0; } } void setup_bme280(void) { if (config_data.debug_level > 0) { gCatena.SafePrintf("Setup_bme280\n"); } if (gBME280.begin(BME280_ADDRESS, Adafruit_BME280::OPERATING_MODE::Sleep)) { fBme = true; } else { fBme = false; gCatena.SafePrintf("No BME280 found: check wiring\n"); } } bool setup_scales(void) { if (config_data.debug_level > 0) { gCatena.SafePrintf("Setup_scales\n"); } bool res; res = true; // Enable Power PowerupScale(); if (config_data.debug_level > 0) { gCatena.SafePrintf("Setup_scale done\n"); } return res; } void setup_flash(void) { if (config_data.debug_level > 0) { gCatena.SafePrintf("setup_flash\n"); } if (gFlash.begin(&gSPI2, Catena::PIN_SPI2_FLASH_SS)) { fFlash = true; gFlash.powerDown(); if (config_data.debug_level > 0) { gCatena.SafePrintf("FLASH found, but power down\n"); } } else { fFlash = false; gFlash.end(); gSPI2.end(); if (config_data.debug_level > 0) { gCatena.SafePrintf("FLASH found: check hardware\n"); } } } void setup_uplink(void) { if (config_data.debug_level > 0) { gCatena.SafePrintf("setup_uplink\n"); } LMIC_setClockError(1 * 65536 / 100); /* figure out when to reboot */ gRebootMs = (CATCFG_T_REBOOT + os_getRndU2() - 32768) * 1000; // Do an unjoin, so every reboot will trigger a join if (config_data.debug_level > 0) { gCatena.SafePrintf("do an unjoin...\n"); } LMIC_unjoin(); /* trigger a join by sending the first packet */ if (!(gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fManufacturingTest))) { if (!gLoRaWAN.IsProvisioned()) gCatena.SafePrintf("LoRaWAN not provisioned yet. Use the commands to set it up.\n"); else { if (config_data.debug_level > 1) { gLed.Set(LedPattern::Joining); } /* trigger a join by sending the first packet */ StartNewIteration(); } } } // The Arduino loop routine -- in our case, we just drive the other loops. // If we try to do too much, we can break the LMIC radio. So the work is // done by outcalls scheduled from the LMIC os loop. void loop() { gCatena.poll(); } void ClearLoraData(void) { lora_data.version = LORA_DATA_VERSION; lora_data.vbat = 0; lora_data.temperature = 0; for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { lora_data.humidity[i] = 0; lora_data.pressure[i] = 0; lora_data.weight[i] = 0; if (i < (MAX_VALUES_TO_SEND - 1)) { lora_data.temperature_change[i] = 0; } } lora_data_first.version = LORA_DATA_VERSION_FIRST_PACKAGE; lora_data_first.fw_version = fwVersion; lora_data_first.vbat = 0; lora_data_first.humidity = 0; lora_data_first.pressure = 0; lora_data_first.weight1 = 0; lora_data_first.weight2 = 0; lora_data_first.cal_w1_0 = config_data.cal_w1_0; lora_data_first.cal_w2_0 = config_data.cal_w2_0; lora_data_first.cal_w1_factor = config_data.cal_w1_factor; lora_data_first.cal_w2_factor = config_data.cal_w2_factor; lora_data_first.temperature = 0; my_position = 0; // We initialize last_sensor_reading last_sensor_reading.vbat = 0; last_sensor_reading.weight1 = 0; last_sensor_reading.weight2 = 0; last_sensor_reading.weight = 0; last_sensor_reading.temperature = 0; last_sensor_reading.humidity = 0; last_sensor_reading.pressure = 0; } void ShowLORAData(bool firstTime) { gCatena.SafePrintf("ShowLORAData\n"); if (firstTime) { gCatena.SafePrintf("{\n"); gCatena.SafePrintf(" \"version\": \"%d\",\n", lora_data_first.version); gCatena.SafePrintf(" \"fw_version\": \"%d\",\n", lora_data_first.fw_version); gCatena.SafePrintf(" \"vbat\": \"%u\",\n", lora_data_first.vbat); gCatena.SafePrintf(" \"humidity\": \"%u\",\n", lora_data_first.humidity); gCatena.SafePrintf(" \"pressure\": \"%u\",\n", lora_data_first.pressure); gCatena.SafePrintf(" \"weight1\": \"%ld\",\n", lora_data_first.weight1); gCatena.SafePrintf(" \"weight2\": \"%ld\",\n", lora_data_first.weight2); gCatena.SafePrintf(" \"cal_w1_0\": \"%ld\",\n", lora_data_first.cal_w1_0); gCatena.SafePrintf(" \"cal_w2_0\": \"%ld\",\n", lora_data_first.cal_w2_0); gCatena.SafePrintf(" \"cal_w1_factor\": \"%d.%03d\",\n", (int)lora_data_first.cal_w1_factor, (int)abs(lora_data_first.cal_w1_factor * 1000) % 1000); gCatena.SafePrintf(" \"cal_w2_factor\": \"%d.%03d\",\n", (int)lora_data_first.cal_w2_factor, (int)abs(lora_data_first.cal_w2_factor * 1000) % 1000); gCatena.SafePrintf(" \"temperature\": \"%u\",\n", lora_data_first.temperature); gCatena.SafePrintf("}\n"); } else { gCatena.SafePrintf("{\n"); gCatena.SafePrintf(" \"version\": \"%d\",\n", lora_data.version); gCatena.SafePrintf(" \"vbat\": \"%u\",\n", lora_data.vbat); gCatena.SafePrintf(" \"temperature\": \"%u\",\n", lora_data.temperature); gCatena.SafePrintf(" \"humidity\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { gCatena.SafePrintf("%d", lora_data.humidity[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { gCatena.SafePrintf(","); } } gCatena.SafePrintf("],\n"); gCatena.SafePrintf(" \"pressure\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { gCatena.SafePrintf("%d", lora_data.pressure[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { gCatena.SafePrintf(","); } } gCatena.SafePrintf("],\n"); gCatena.SafePrintf(" \"weight\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { gCatena.SafePrintf("%ld", lora_data.weight[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { gCatena.SafePrintf(","); } } gCatena.SafePrintf("],\n"); gCatena.SafePrintf(" \"temperature_change\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND - 1; i++) { gCatena.SafePrintf("%d", lora_data.temperature_change[i]); if (i < (MAX_VALUES_TO_SEND - 2)) { gCatena.SafePrintf(","); } } gCatena.SafePrintf("]\n"); gCatena.SafePrintf("}\n"); } } uint8_t GetVBatValue(int millivolts) { uint8_t res; if (millivolts <= 2510) { res = 0; } else if (millivolts >= 4295) { res = 255; } else { res = (millivolts - 2510) / 7; } return res; } void DoDeepSleep(uint32_t sleep_time) { if (config_data.debug_level > 0) { gCatena.SafePrintf("DoDeepSleep, now going to deep sleep\n"); } // Prepare Deep Sleep if (config_data.debug_level > 1) { gLed.Set(LedPattern::Off); } deepSleepPrepare(); // Now sleeping... gCatena.Sleep(sleep_time); // Recover from wakeup... deepSleepRecovery(); if (config_data.debug_level > 0) { gCatena.SafePrintf("done with deep sleep\n"); } } void ReadSensors(SENSOR_data &sensor_data) { SENSOR_data res; int32_t weight_current32; long w1_0_real; long w2_0_real; // vBat int vbat_mv = (int)(gCatena.ReadVbat() * 1000.0f); res.vbat = GetVBatValue(vbat_mv); if (config_data.debug_level > 0) { gCatena.SafePrintf("vBat: %d mV\n", vbat_mv); } // Read Scales w1_0_real = config_data.cal_w1_0; w2_0_real = config_data.cal_w2_0; if (setup_scales()) { if (config_data.debug_level > 0) { gCatena.SafePrintf("LoadCell is ready.\n"); } if (config_data.cal_w1_0 != NOT_ATTACHED) { res.weight1 = (int32_t)ReadScale('A'); if (config_data.debug_level > 0) { gCatena.SafePrintf("Load_cell 1 weight1_current: %ld\n", res.weight1); } } else { res.weight1 = 0; w1_0_real = 0; if (config_data.debug_level > 0) { gCatena.SafePrintf("Load_cell 1 is disabled\n"); } } if (config_data.cal_w2_0 != NOT_ATTACHED) { res.weight2 = (int32_t)ReadScale('B'); if (config_data.debug_level > 0) { gCatena.SafePrintf("Load_cell 2 weight2_current: %ld\n", res.weight2); } } else { res.weight2 = 0; w2_0_real = 0; if (config_data.debug_level > 0) { gCatena.SafePrintf("Load_cell 2 is disabled\n"); } } } else { if (config_data.debug_level > 0) { gCatena.SafePrintf("LoadCell not ready.\n"); } } // Disable Power PowerdownScale(); // Gewicht berechnen weight_current32 = (int32_t)((((res.weight1 - w1_0_real) / config_data.cal_w1_factor) + ((res.weight2 - w2_0_real) / config_data.cal_w2_factor)) / 5.0); if (weight_current32 < 0) { weight_current32 = 0; } else if (weight_current32 > UINT16_MAX) { //weight_current32 = UINT16_MAX; // we set the weight to 0, as such high values are not realistic and probably a sign for bad calibration... weight_current32 = 0; } if (config_data.cal_w1_0 == NOT_ATTACHED || config_data.cal_w2_0 == NOT_ATTACHED) { // when at least one load cell is disabled, we multiply the measured weight by 2 weight_current32 = weight_current32 * 2; } res.weight = (uint16_t)weight_current32; if (fBme) { /* warm up the BME280 by discarding a measurement */ (void)gBME280.readTemperature(); Adafruit_BME280::Measurements m = gBME280.readTemperaturePressureHumidity(); // temperature is 2 bytes from -0x80.00 to +0x7F.FF degrees C // pressure is 2 bytes, hPa * 10. // humidity is one byte, where 0 == 0/256 and 0xFF == 255/256. if (config_data.debug_level > 0) { gCatena.SafePrintf( "BME280: T: %d P: %d RH: %d\n", (int)m.Temperature, (int)m.Pressure, (int)m.Humidity); } res.temperature = (int16_t)((m.Temperature) * 10); res.humidity = (uint8_t)m.Humidity; res.pressure = (uint8_t)((m.Pressure / 100) - PRESSURE_OFFSET); if (config_data.debug_level > 0) { gCatena.SafePrintf("pressure_current: %d\n", res.pressure); } } sensor_data = res; } void StartNewIteration() { uint32_t wait_time; wait_time = 0; // we increment the iteration counter iteration++; SENSOR_data current_sensor_reading; ReadSensors(current_sensor_reading); int16_t temp_change; // vBus float vBus = gCatena.ReadVbus(); if (config_data.debug_level > 0) { gCatena.SafePrintf("vBus: %d mV\n", (int)(vBus * 1000.0f)); } fUsbPower = (vBus > 4.3) ? true : false; if (next_package_is_init_package) { lora_data_first.vbat = current_sensor_reading.vbat; lora_data_first.weight1 = current_sensor_reading.weight1; lora_data_first.weight2 = current_sensor_reading.weight2; lora_data_first.temperature = current_sensor_reading.temperature; lora_data_first.humidity = current_sensor_reading.humidity; lora_data_first.pressure = current_sensor_reading.pressure; } else { lora_data.vbat = current_sensor_reading.vbat; lora_data.weight[my_position] = current_sensor_reading.weight; if (my_position == 0) { lora_data.temperature = current_sensor_reading.temperature; } else { temp_change = current_sensor_reading.temperature - last_sensor_reading.temperature; if (temp_change > 127) { temp_change = 127; } if (temp_change < -128) { temp_change = -128; } lora_data.temperature_change[my_position - 1] = (uint8_t)temp_change; } lora_data.humidity[my_position] = current_sensor_reading.humidity; lora_data.pressure[my_position] = current_sensor_reading.pressure; } if (my_position == 0) { timer_pos0 = millis(); } if (config_data.debug_level > 0) { ShowLORAData(next_package_is_init_package); } my_position++; // Should we send the Data? // we send data the first time the system is started, when the array is full // or when the weight has fallen more than threshold or the first measurement is // more than one hour old (which should not happen :-) ) if ( (next_package_is_init_package) || (my_position >= MAX_VALUES_TO_SEND) || (abs(last_sensor_reading.weight - current_sensor_reading.weight) > SEND_DIFF_THRESHOLD_5GRAMS) || ((millis() - timer_pos0) > 3600000)) { lora_data.offset_last_reading = (uint8_t)((millis() - timer_pos0) / 1000 / 60); if (config_data.debug_level > 0) { gCatena.SafePrintf("startSendingUplink(), my_position: %d, iteration: %d, package_counter: %d\n", my_position, iteration, package_counter); } // the first packets are "Init-Packets" or each INIT_PACKAGE_INTERVAL ... startSendingUplink(next_package_is_init_package); next_package_is_init_package = ((iteration < INIT_PACKETS) || ((package_counter % INIT_PACKAGE_INTERVAL) == 0)); if (config_data.debug_level > 1) { gLed.Set(LedPattern::TwoShort); } // Loop while sending is in progress, timeout just in case after 300 seconds long start_time = millis(); if (config_data.debug_level > 0) { gCatena.SafePrintf("waiting while send is in progress\n"); } while (send_in_progress && ((millis() - start_time) < 300000)) { os_runloop_once(); // gCatena.poll(); // yield(); } // handle timeout... if (send_in_progress) { if (config_data.debug_level > 0) { gCatena.SafePrintf("looks like we timed out waiting for sending to finish...\n", wait_time); } send_in_progress = false; } wait_time = (uint32_t)((millis() - start_time) / 1000); if (config_data.debug_level > 0) { gCatena.SafePrintf("end waiting, wait time was %d seconds\n", wait_time); } } if (not(next_package_is_init_package)) { // we make the current sensor reading to the last one... last_sensor_reading = current_sensor_reading; } uint32_t sleep_time_sec; if (wait_time < CATCFG_T_INTERVAL) { sleep_time_sec = CATCFG_T_INTERVAL - wait_time; } else { // minimal sleep time is 5 seconds sleep_time_sec = 5; } // for the first iterations, we set the sleep time to 10 seconds only... if (iteration <= INIT_PACKETS) { sleep_time_sec = 10; } if (config_data.debug_level > 0) { gCatena.SafePrintf("now going to sleep for %d seconds...\n", sleep_time_sec); if (fUsbPower) { gCatena.SafePrintf("USB Power is on\n"); } else { gCatena.SafePrintf("USB Power is off\n"); } //Serial.flush(); if (config_data.debug_level > 1) { gLed.Set(LedPattern::Sleeping); } } if (!fUsbPower) { DoDeepSleep(sleep_time_sec); if (! stop_iterations) { StartNewIteration(); } //os_setTimedCallback( // &iterationJob, // os_getTime() + sec2osticks(2), // startNewIterationCb); } else { if (config_data.debug_level > 0) { gCatena.SafePrintf("light sleep; os_setTimedCallback for startNewIterationCb in %d...seconds\n", sleep_time_sec); } os_setTimedCallback( &iterationJob, os_getTime() + sec2osticks(sleep_time_sec), startNewIterationCb); } } void startSendingUplink(bool firstTime) { send_in_progress = true; LedPattern savedLed; if (config_data.debug_level > 1) { savedLed = gLed.Set(LedPattern::Measuring); } if (config_data.debug_level > 1) { if (savedLed != LedPattern::Joining) gLed.Set(LedPattern::Sending); else gLed.Set(LedPattern::Joining); } bool fConfirmed = false; if (gCatena.GetOperatingFlags() & (1 << 16)) { if (config_data.debug_level > 0) { gCatena.SafePrintf("requesting confirmed tx\n"); } fConfirmed = true; } os_runloop_once(); //gCatena.poll(); if (firstTime) { if (config_data.debug_level > 0) { gCatena.SafePrintf("SendBuffer firstTime\n"); } gLoRaWAN.SendBuffer((uint8_t*)&lora_data_first, sizeof(LORA_data_first), sendBufferDoneCb, NULL, fConfirmed, kUplinkPort); package_counter++; } else { if (config_data.debug_level > 0) { gCatena.SafePrintf("SendBuffer not firstTime\n"); } gLoRaWAN.SendBuffer((uint8_t*)&lora_data, sizeof(LORA_data), sendBufferDoneCb, NULL, fConfirmed, kUplinkPort); package_counter++; } ClearLoraData(); } static void sendBufferDoneCb( void* pContext, bool fStatus) { osjobcb_t pFn; if (config_data.debug_level > 1) { gLed.Set(LedPattern::Settling); } pFn = settleDoneCb; if (! fStatus) { send_in_progress = false; if (!gLoRaWAN.IsProvisioned()) { // we'll talk about it at the callback. pFn = txNotProvisionedCb; // but prevent join attempts now. gLoRaWAN.Shutdown(); } else if (config_data.debug_level > 0) { gCatena.SafePrintf("send buffer failed\n"); } } os_setTimedCallback( &sendJob, os_getTime() + sec2osticks(CATCFG_T_SETTLE), pFn ); } static void txNotProvisionedCb( osjob_t *pSendJob ) { if (config_data.debug_level > 0) { gCatena.SafePrintf("LoRaWAN not provisioned yet. Use the commands to set it up.\n"); } gLoRaWAN.Shutdown(); if (config_data.debug_level > 1) { gLed.Set(LedPattern::NotProvisioned); } } static void settleDoneCb( osjob_t* pSendJob) { const bool fDeepSleep = checkDeepSleep(); if (config_data.debug_level > 0) { gCatena.SafePrintf("settleDoneCb\n"); } if (config_data.debug_level > 2) { // Terry vv gCatena.SafePrintf("LMIC.rxDelay: %i\n", LMIC.rxDelay); gCatena.SafePrintf("LMIC.dn2Dr: %i\n", LMIC.dn2Dr); gCatena.SafePrintf("LMIC.dn2Freq: %i\n", LMIC.dn2Freq); gCatena.SafePrintf("LMIC.rx1DrOffset: %i\n", LMIC.rx1DrOffset); gCatena.SafePrintf("LMIC.adrAckReq: %i\n", LMIC.adrAckReq); gCatena.SafePrintf("LMIC.adrEnabled: %i\n", LMIC.adrEnabled); // Terry ^^ } if (uint32_t(millis()) > gRebootMs) { // time to reboot NVIC_SystemReset(); } if (! g_fPrintedSleeping) doSleepAlert(fDeepSleep); /* count what we're up to */ updateSleepCounters(); if (fDeepSleep) doDeepSleep(pSendJob); else doLightSleep(pSendJob); } bool checkDeepSleep(void) { bool const fDeepSleepTest = gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); bool fDeepSleep; if (fDeepSleepTest) { fDeepSleep = true; } #ifdef USBCON else if (Serial.dtr()) { fDeepSleep = false; } #endif else if (gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fDisableDeepSleep)) { fDeepSleep = false; } else if ((gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fUnattended)) != 0) { fDeepSleep = true; } else { fDeepSleep = false; } return fDeepSleep; } void doSleepAlert(const bool fDeepSleep) { g_fPrintedSleeping = true; if (fDeepSleep) { bool const fDeepSleepTest = gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); const uint32_t deepSleepDelay = fDeepSleepTest ? 10 : 30; if (config_data.debug_level > 2) { gCatena.SafePrintf("using deep sleep in %u secs" #ifdef USBCON " (USB will disconnect while asleep)" #endif ": ", deepSleepDelay ); } // sleep and print if (config_data.debug_level > 1) { gLed.Set(LedPattern::TwoShort); } for (auto n = deepSleepDelay; n > 0; --n) { uint32_t tNow = millis(); while (uint32_t(millis() - tNow) < 1000) { os_runloop_once(); //gCatena.poll(); //yield(); } if (config_data.debug_level > 2) { gCatena.SafePrintf("."); } } if (config_data.debug_level > 2) { gCatena.SafePrintf("\nStarting deep sleep.\n"); } uint32_t tNow = millis(); while (uint32_t(millis() - tNow) < 100) { os_runloop_once(); //gCatena.poll(); //yield(); } } else if (config_data.debug_level > 2) { gCatena.SafePrintf("using light sleep\n"); } } void updateSleepCounters(void) { // update the sleep parameters if (gTxCycleCount > 1) { // values greater than one are decremented and ultimately reset to default. --gTxCycleCount; } else if (gTxCycleCount == 1) { // it's now one (otherwise we couldn't be here.) if (config_data.debug_level > 2) { gCatena.SafePrintf("resetting tx cycle to default: %u\n", CATCFG_T_CYCLE); } gTxCycleCount = 0; gTxCycle = CATCFG_T_CYCLE; } else { // it's zero. Leave it alone. } } void doDeepSleep(osjob_t *pJob) { bool const fDeepSleepTest = gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fDeepSleepTest); uint32_t const sleepInterval = CATCFG_GetInterval( fDeepSleepTest ? CATCFG_T_CYCLE_TEST : gTxCycle ); if (config_data.debug_level > 0) { gCatena.SafePrintf("doDeepSleep, sleepInterval: %d...\n", sleepInterval); } /* ok... now it's time for a deep sleep */ gLed.Set(LedPattern::Off); deepSleepPrepare(); /* sleep */ gCatena.Sleep(sleepInterval); /* recover from sleep */ deepSleepRecovery(); /* and now... we're awake again. trigger another measurement */ sleepDoneCb(pJob); } void deepSleepPrepare(void) { Serial.end(); Wire.endTransmission(true); Wire.end(); SPI.end(); if (fFlash) gSPI2.end(); } void deepSleepRecovery(void) { Serial.begin(); Wire.begin(); SPI.begin(); if (fFlash) gSPI2.begin(); } void doLightSleep(osjob_t *pJob) { uint32_t interval = sec2osticks(CATCFG_GetInterval(gTxCycle)); if (config_data.debug_level > 1) { gLed.Set(LedPattern::Sleeping); gCatena.SafePrintf("doLightSleep\n"); } if (gCatena.GetOperatingFlags() & static_cast(gCatena.OPERATING_FLAGS::fQuickLightSleep)) { interval = 1; } os_setTimedCallback( &iterationJob, os_getTime() + interval, sleepDoneCb ); } static void sleepDoneCb(osjob_t* pJob) { if (config_data.debug_level > 1) { gLed.Set(LedPattern::WarmingUp); } if (config_data.debug_level > 0) { gCatena.SafePrintf("sleepDoneCb\n"); } os_setTimedCallback( pJob, os_getTime() + sec2osticks(CATCFG_T_WARMUP), warmupDoneCb); } static void warmupDoneCb(osjob_t* pJob) { if (config_data.debug_level > 0) { gCatena.SafePrintf("warmupDoneCb\n"); } send_in_progress = false; } static void startNewIterationCb(osjob_t* pJob) { if (config_data.debug_level > 0) { gCatena.SafePrintf("startNewIterationCb\n"); } if (! stop_iterations) { StartNewIteration(); } } static void receiveMessage(void *pContext, uint8_t port, const uint8_t *pMessage, size_t nMessage) { unsigned txCycle; unsigned txCount; long cal_w1_0; long cal_w2_0; float cal_w1_factor; float cal_w2_factor; union u_tag { byte b[4]; float fval; } u; if (config_data.debug_level > 0) { gCatena.SafePrintf("receiveMessage was called!!!\n"); } if (config_data.debug_level > 2) { // Terry vv if (port == 0) { gCatena.SafePrintf("MAC message:"); for (unsigned i = 0; i < LMIC.dataBeg; ++i) { gCatena.SafePrintf(" %02x", LMIC.frame[i]); } return; } gCatena.SafePrintf("Port: %i\n", port); gCatena.SafePrintf("LMIC.rxDelay: %i\n", LMIC.rxDelay); gCatena.SafePrintf("LMIC.dn2Dr: %i\n", LMIC.dn2Dr); gCatena.SafePrintf("LMIC.dn2Freq: %i\n", LMIC.dn2Freq); gCatena.SafePrintf("LMIC.rx1DrOffset: %i\n", LMIC.rx1DrOffset); // Terry ^^ } if (config_data.debug_level > 0) { gCatena.SafePrintf("message received port(%02x)/length(%x)\n", port, nMessage ); // we print out the received message... gCatena.SafePrintf("Current LMIC.seqnoUp: %d\n", LMIC.seqnoUp); gCatena.SafePrintf("Received Data (Payload): \n"); for (byte i = 0; i < nMessage; i++) { gCatena.SafePrintf("%02x", pMessage[i]); } gCatena.SafePrintf("\n"); } if (LMIC.seqnoUp > 5) { if (config_data.debug_level > 0) { gCatena.SafePrintf("setting calibration config with downlink is only possible within first five uplink packets!\n"); return; } } if (port == 1 && nMessage == 17) { cal_w1_0 = 0; cal_w1_0 += (long)pMessage[1] << 24; cal_w1_0 += (long)pMessage[2] << 16; cal_w1_0 += (long)pMessage[3] << 8; cal_w1_0 += (long)pMessage[4]; cal_w2_0 = 0; cal_w2_0 += (long)pMessage[5] << 24; cal_w2_0 += (long)pMessage[6] << 16; cal_w2_0 += (long)pMessage[7] << 8; cal_w2_0 += (long)pMessage[8]; u.b[0] = pMessage[12]; u.b[1] = pMessage[11]; u.b[2] = pMessage[10]; u.b[3] = pMessage[9]; cal_w1_factor = u.fval; u.b[0] = pMessage[16]; u.b[1] = pMessage[15]; u.b[2] = pMessage[14]; u.b[3] = pMessage[13]; cal_w2_factor = u.fval; if (pMessage[0] == 0) { // set both scales to 0, use transmitted values (offset only) if (config_data.debug_level > 0) { gCatena.SafePrintf("set calibration to zero, cal_w1_0: %d, cal_w2_0: %d\n", cal_w1_0, cal_w2_0); } config_data.cal_w1_0 = cal_w1_0; config_data.cal_w2_0 = cal_w2_0; gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); lora_data_first.cal_w1_0 = config_data.cal_w1_0; lora_data_first.cal_w2_0 = config_data.cal_w2_0; } if (pMessage[0] == 1) { // update calibration config with transmitted values if (config_data.debug_level > 0) { gCatena.SafePrintf("update calibration config, cal_w1_0: %d, cal_w2_0: %d, cal_w1_factor: %d.%03d, cal_w2_factor: %d.%03d\n", cal_w1_0, cal_w2_0, (int)cal_w1_factor, (int)abs(cal_w1_factor * 1000) % 1000, (int)cal_w2_factor, (int)abs(cal_w2_factor * 1000) % 1000); } config_data.cal_w1_0 = cal_w1_0; config_data.cal_w2_0 = cal_w2_0; config_data.cal_w1_factor = cal_w1_factor; config_data.cal_w2_factor = cal_w2_factor; gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); lora_data_first.cal_w1_0 = config_data.cal_w1_0; lora_data_first.cal_w2_0 = config_data.cal_w2_0; lora_data_first.cal_w1_factor = config_data.cal_w1_factor; lora_data_first.cal_w2_factor = config_data.cal_w2_factor; } } if (port == 0) { return; } else if (! (port == 1 && 2 <= nMessage && nMessage <= 3)) { if (config_data.debug_level > 0) { gCatena.SafePrintf("invalid message port(%02x)/length(%x)\n", port, nMessage ); } return; } txCycle = (pMessage[0] << 8) | pMessage[1]; if (txCycle < CATCFG_T_MIN || txCycle > CATCFG_T_MAX) { if (config_data.debug_level > 0) { gCatena.SafePrintf("tx cycle time out of range: %u\n", txCycle); } return; } // byte [2], if present, is the repeat count. // explicitly sending zero causes it to stick. txCount = CATCFG_INTERVAL_COUNT; if (nMessage >= 3) { txCount = pMessage[2]; } setTxCycleTime(txCycle, txCount); } void setTxCycleTime(unsigned txCycle, unsigned txCount) { if (txCount > 0) { if (config_data.debug_level > 0) { gCatena.SafePrintf("message cycle time %u seconds for %u messages\n", txCycle, txCount); } } else if (config_data.debug_level > 0) { gCatena.SafePrintf("message cycle time %u seconds indefinitely\n", txCycle); } gTxCycle = txCycle; gTxCycleCount = txCount; } /* process "application hello" -- args are ignored */ // argv[0] is "hello" // argv[1..argc-1] are the (ignored) arguments cCommandStream::CommandStatus cmdHello(cCommandStream * pThis, void *pContext, int argc, char **argv) { pThis->printf("Hello, world!\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdGetCalibrationSettings(cCommandStream * pThis, void *pContext, int argc, char **argv) { pThis->printf("{\n"); pThis->printf(" \"cal_w1_0\": \"%d\",\n", config_data.cal_w1_0); pThis->printf(" \"cal_w2_0\": \"%d\",\n", config_data.cal_w2_0); pThis->printf(" \"cal_w1_factor\": \"%d.%03d\n", (int)config_data.cal_w1_factor, (int)abs(config_data.cal_w1_factor * 1000) % 1000); pThis->printf(" \"cal_w2_factor\": \"%d.%03d\n", (int)config_data.cal_w2_factor, (int)abs(config_data.cal_w2_factor * 1000) % 1000); pThis->printf("}\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdGetSensorReadings(cCommandStream * pThis, void *pContext, int argc, char **argv) { SENSOR_data temp_sensor_data; ReadSensors(temp_sensor_data); pThis->printf("{\n"); pThis->printf(" \"weight\": \"%d\",\n", temp_sensor_data.weight); pThis->printf(" \"weight1_raw\": \"%d\",\n", temp_sensor_data.weight1); pThis->printf(" \"weight2_raw\": \"%d\",\n", temp_sensor_data.weight2); pThis->printf(" \"temperature\": \"%d\",\n", temp_sensor_data.temperature); pThis->printf(" \"humidity\": \"%d\",\n", temp_sensor_data.humidity); pThis->printf(" \"pressure\": \"%d\",\n", temp_sensor_data.pressure); pThis->printf(" \"batt\": \"%d\",\n", temp_sensor_data.vbat); pThis->printf("}\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdGetScale1(cCommandStream * pThis, void *pContext, int argc, char **argv) { pThis->printf("getscale1\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdGetScale2(cCommandStream * pThis, void *pContext, int argc, char **argv) { pThis->printf("getscale2\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdCalibrateZeroScaleA(cCommandStream * pThis, void *pContext, int argc, char **argv) { setup_scales(); config_data.cal_w1_0 = (int32_t)ReadScale('A'); gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); pThis->printf("{ \"msg\": \"calibrate_zero_scale_a was successful\" }\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdCalibrateZeroScaleB(cCommandStream * pThis, void *pContext, int argc, char **argv) { setup_scales(); config_data.cal_w2_0 = (int32_t)ReadScale('B'); gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); pThis->printf("{ \"msg\": \"calibrate_zero_scale_b was successful\" }\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdCalibrateScaleA(cCommandStream * pThis, void *pContext, int argc, char **argv) { String w1_gramm(argv[1]); long weight1; if (w1_gramm == "NA") { // scale a is not connected config_data.cal_w1_factor = 1.0; config_data.cal_w1_0 = NOT_ATTACHED; } else { setup_scales(); weight1 = ReadScale('A'); config_data.cal_w1_factor = (float)((weight1 - config_data.cal_w1_0) / w1_gramm.toFloat()); } gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); pThis->printf("{ \"msg\": \"calibrate_scale_a was successful\" }\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdCalibrateScaleB(cCommandStream * pThis, void *pContext, int argc, char **argv) { String w2_gramm(argv[1]); long weight2; if (w2_gramm == "NA") { // scale b is not connected config_data.cal_w2_factor = 1.0; config_data.cal_w2_0 = NOT_ATTACHED; } else { setup_scales(); weight2 = ReadScale('B'); config_data.cal_w2_factor = (float)((weight2 - config_data.cal_w2_0) / w2_gramm.toFloat()); } gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); pThis->printf("{ \"msg\": \"calibrate_scale_b was successful\" }\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdSetDebugLevel(cCommandStream * pThis, void *pContext, int argc, char **argv) { String s_debug_level(argv[1]); config_data.debug_level = s_debug_level.toInt(); gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); SetScalesDebugLevel(config_data.debug_level); pThis->printf("{ \"msg\": \"set_debug_level was successful\" }\n"); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdGetDebugLevel(cCommandStream * pThis, void *pContext, int argc, char **argv) { gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); pThis->printf("{ \"msg\": \"debug_level is %d\" }\n", config_data.debug_level); return cCommandStream::CommandStatus::kSuccess; } cCommandStream::CommandStatus cmdStopIterations(cCommandStream * pThis, void *pContext, int argc, char **argv) { stop_iterations = true; return cCommandStream::CommandStatus::kSuccess; }