This commit is contained in:
Oliver Brandmueller 2018-11-22 20:15:11 +01:00
commit 760ddb8c84
83 changed files with 12255 additions and 664 deletions

View File

@ -14,7 +14,7 @@ Paxcounter is a proof-of-concept device for metering passenger flows in realtime
Intention of this project is to do this without intrusion in privacy: You don't need to track people owned devices, if you just want to count them. Therefore, Paxcounter does not persistenly store MAC adresses and does no kind of fingerprinting the scanned devices. Intention of this project is to do this without intrusion in privacy: You don't need to track people owned devices, if you just want to count them. Therefore, Paxcounter does not persistenly store MAC adresses and does no kind of fingerprinting the scanned devices.
Metered counts are transferred to a server via a LoRaWAN network, and/or a local SPI cable interface. Data is transferred to a server via a LoRaWAN network, and/or a wired SPI slave interface.
You can build this project battery powered and reach a full day uptime with a single 18650 Li-Ion cell. You can build this project battery powered and reach a full day uptime with a single 18650 Li-Ion cell.
@ -33,19 +33,21 @@ This can all be done with a single small and cheap ESP32 board for less than $20
LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-Lora) LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-Lora)
- Adafruit ESP32 Feather + LoRa Wing + OLED Wing, #IoT Octopus32 (Octopus + ESP32 Feather) - Adafruit ESP32 Feather + LoRa Wing + OLED Wing, #IoT Octopus32 (Octopus + ESP32 Feather)
*SPI only*: (code yet to come) *SPI only*:
- Pyom: WiPy - Pyom: WiPy
- WeMos: LoLin32, LoLin32 Lite, WeMos D32 - WeMos: LoLin32, LoLin32 Lite, WeMos D32
- Generic ESP32
Depending on board hardware following features are supported: Depending on board hardware following features are supported:
- LED - LED (power/status)
- OLED Display - OLED Display (detailed status)
- RGB LED - RGB LED (colorized status)
- Button - Button
- Silicon unique ID - Silicon unique ID
- Battery voltage monitoring - Battery voltage monitoring
- GPS (Generic serial NMEA, or Quectel L76 I2C) - GPS (Generic serial NMEA, or Quectel L76 I2C)
- MEMS sensor (Bosch BME680)
Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/platformio.ini).<br> Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/platformio.ini).<br>
Hardware dependent settings (pinout etc.) are stored in board files in /hal directory. If you want to use a ESP32 board which is not yet supported, use hal file generic.h and tailor pin mappings to your needs. Pull requests for new boards welcome.<br> Hardware dependent settings (pinout etc.) are stored in board files in /hal directory. If you want to use a ESP32 board which is not yet supported, use hal file generic.h and tailor pin mappings to your needs. Pull requests for new boards welcome.<br>
@ -197,6 +199,13 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering.
byte 1: Beacon RSSI reception level byte 1: Beacon RSSI reception level
byte 2: Beacon identifier (0..255) byte 2: Beacon identifier (0..255)
**Port #7:** BME680 query result
bytes 1-2: Temperature [°C]
bytes 3-4: Pressure [hPa]
bytes 5-6: Humidity [%]
bytes 7-8: Gas resistance [kOhm]
# Remote control # Remote control
The device listenes for remote control commands on LoRaWAN Port 2. Multiple commands per downlink are possible by concatenating them. The device listenes for remote control commands on LoRaWAN Port 2. Multiple commands per downlink are possible by concatenating them.
@ -309,6 +318,10 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts.
Device answers with it's current status on Port 4. Device answers with it's current status on Port 4.
0x85 get BME680 sensor data
Device answers with BME680 sensor data set on Port 7.
# License # License

15
include/bme680read.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef _HAS_BME
#define _HAS_BME
#include "globals.h"
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
extern bmeStatus_t
bme_status; // Make struct for storing gps data globally available
void bme_init();
bool bme_read();
#endif

View File

@ -3,8 +3,12 @@
#include "globals.h" #include "globals.h"
#include "senddata.h" #include "senddata.h"
#include "rcommand.h"
#include "spislave.h"
#include <lmic.h>
void doHousekeeping(void); void doHousekeeping(void);
void do_timesync(void);
uint64_t uptime(void); uint64_t uptime(void);
void reset_counters(void); void reset_counters(void);
int redirect_log(const char *fmt, va_list args); int redirect_log(const char *fmt, va_list args);

View File

@ -38,6 +38,21 @@ typedef struct {
uint8_t Message[PAYLOAD_BUFFER_SIZE]; uint8_t Message[PAYLOAD_BUFFER_SIZE];
} MessageBuffer_t; } MessageBuffer_t;
typedef struct {
uint32_t latitude;
uint32_t longitude;
uint8_t satellites;
uint16_t hdop;
uint16_t altitude;
} gpsStatus_t;
typedef struct {
float temperature; // Temperature in degrees Centigrade
uint16_t pressure; // Barometic pressure in hecto pascals
float humidity; // Relative humidity in percent
uint16_t gas_resistance; // Resistance in MOhms
} bmeStatus_t;
// global variables // global variables
extern configData_t cfg; // current device configuration extern configData_t cfg; // current device configuration
extern char display_line6[], display_line7[]; // screen buffers extern char display_line6[], display_line7[]; // screen buffers
@ -52,24 +67,21 @@ extern std::array<uint64_t, 0xff> beacons;
extern TaskHandle_t irqHandlerTask, wifiSwitchTask; extern TaskHandle_t irqHandlerTask, wifiSwitchTask;
#include "led.h"
#include "payload.h"
#ifdef HAS_GPS #ifdef HAS_GPS
#include "gpsread.h" #include "gpsread.h"
#endif #endif
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) #ifdef HAS_BME
#include "led.h" #include "bme680read.h"
#endif #endif
#include "payload.h"
#ifdef HAS_LORA #ifdef HAS_LORA
#include "lorawan.h" #include "lorawan.h"
#endif #endif
#ifdef HAS_SPI
#include "spisend.h"
#endif
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
#include "display.h" #include "display.h"
#endif #endif
@ -90,8 +102,4 @@ extern TaskHandle_t irqHandlerTask, wifiSwitchTask;
#include "antenna.h" #include "antenna.h"
#endif #endif
void reset_counters(void);
void blink_LED(uint16_t set_color, uint16_t set_blinkduration);
uint64_t uptime();
#endif #endif

View File

@ -8,14 +8,6 @@
#include <Wire.h> #include <Wire.h>
#endif #endif
typedef struct {
uint32_t latitude;
uint32_t longitude;
uint8_t satellites;
uint16_t hdop;
uint16_t altitude;
} gpsStatus_t;
extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe
extern gpsStatus_t extern gpsStatus_t
gps_status; // Make struct for storing gps data globally available gps_status; // Make struct for storing gps data globally available

View File

@ -3,7 +3,7 @@
#define DISPLAY_IRQ 0x01 #define DISPLAY_IRQ 0x01
#define BUTTON_IRQ 0x02 #define BUTTON_IRQ 0x02
#define SENDPAYLOAD_IRQ 0x04 #define SENDCOUNTER_IRQ 0x04
#define CYCLIC_IRQ 0x08 #define CYCLIC_IRQ 0x08
#include "globals.h" #include "globals.h"

View File

@ -37,8 +37,6 @@ extern TaskHandle_t ledLoopTask;
void rgb_set_color(uint16_t hue); void rgb_set_color(uint16_t hue);
void blink_LED(uint16_t set_color, uint16_t set_blinkduration); void blink_LED(uint16_t set_color, uint16_t set_blinkduration);
void ledLoop(void *parameter); void ledLoop(void *parameter);
#if (HAS_LED != NOT_A_PIN)
void switch_LED(uint8_t state); void switch_LED(uint8_t state);
#endif
#endif #endif

View File

@ -26,5 +26,12 @@ void os_getDevEui(u1_t *buf);
void showLoraKeys(void); void showLoraKeys(void);
void switch_lora(uint8_t sf, uint8_t tx); void switch_lora(uint8_t sf, uint8_t tx);
void lora_send(osjob_t *job); void lora_send(osjob_t *job);
void lora_enqueuedata(MessageBuffer_t *message);
void lora_queuereset(void);
void lora_housekeeping(void);
void user_request_network_time_callback(void *pVoidUserUTCTime,
int flagSuccess);
esp_err_t lora_stack_init();
#endif #endif

View File

@ -13,5 +13,7 @@
#include "ota.h" #include "ota.h"
#include "irqhandler.h" #include "irqhandler.h"
#include "led.h" #include "led.h"
#include "spislave.h"
#include "lorawan.h"
#endif #endif

View File

@ -4,14 +4,15 @@
#ifdef USE_OTA #ifdef USE_OTA
#include "globals.h" #include "globals.h"
#include "update.h"
#include "battery.h" #include "battery.h"
#include "update.h"
#include <WiFi.h> #include <WiFi.h>
#include <WiFiClientSecure.h> #include <WiFiClientSecure.h>
#include <BintrayClient.h> #include <BintrayClient.h>
#include <string> #include <string>
#include <algorithm>
void do_ota_update(); int do_ota_update();
void start_ota_update(); void start_ota_update();
int version_compare(const String v1, const String v2); int version_compare(const String v1, const String v2);
void display(const uint8_t row, const std::string status, void display(const uint8_t row, const std::string status,

View File

@ -10,19 +10,25 @@
#define LPP_BATT_CHANNEL 23 #define LPP_BATT_CHANNEL 23
#define LPP_BUTTON_CHANNEL 24 #define LPP_BUTTON_CHANNEL 24
#define LPP_ADR_CHANNEL 25 #define LPP_ADR_CHANNEL 25
#define LPP_TEMP_CHANNEL 26 #define LPP_TEMPERATURE_CHANNEL 26
#define LPP_ALARM_CHANNEL 27 #define LPP_ALARM_CHANNEL 27
#define LPP_MSG_CHANNEL 28 #define LPP_MSG_CHANNEL 28
#define LPP_HUMIDITY_CHANNEL 29
#define LPP_BAROMETER_CHANNEL 30
#define LPP_GAS_CHANNEL 31
#endif #endif
// MyDevices CayenneLPP types // MyDevices CayenneLPP types
#define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01m #define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01m
#define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed #define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed MSB
#define LPP_DIGITAL_INPUT 0 // 1 byte #define LPP_DIGITAL_INPUT 0 // 1 byte
#define LPP_DIGITAL_OUTPUT 1 // 1 byte #define LPP_DIGITAL_OUTPUT 1 // 1 byte
#define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed #define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed
#define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned
#define LPP_PRESENCE 102 // 1 byte #define LPP_PRESENCE 102 // 1 byte
#define LPP_HUMIDITY 104 // 1 byte, 0.5 % unsigned
#define LPP_BAROMETER 115 // 2 bytes, hPa unsigned MSB
class PayloadConvert { class PayloadConvert {
@ -38,12 +44,9 @@ public:
void addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem, void addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem,
uint8_t reset1, uint8_t reset2); uint8_t reset1, uint8_t reset2);
void addAlarm(int8_t rssi, uint8_t message); void addAlarm(int8_t rssi, uint8_t message);
#ifdef HAS_GPS
void addGPS(gpsStatus_t value); void addGPS(gpsStatus_t value);
#endif void addBME(bmeStatus_t value);
#ifdef HAS_BUTTON
void addButton(uint8_t value); void addButton(uint8_t value);
#endif
#if PAYLOAD_ENCODER == 1 // format plain #if PAYLOAD_ENCODER == 1 // format plain

View File

@ -2,6 +2,7 @@
#define _RCOMMAND_H #define _RCOMMAND_H
#include "senddata.h" #include "senddata.h"
#include "cyclic.h"
#include "configmanager.h" #include "configmanager.h"
#include "lorawan.h" #include "lorawan.h"
#include "macsniff.h" #include "macsniff.h"

View File

@ -1,8 +1,12 @@
#ifndef _SENDDATA_H #ifndef _SENDDATA_H
#define _SENDDATA_H #define _SENDDATA_H
void SendData(uint8_t port); #include "spislave.h"
void sendPayload(void); #include "lorawan.h"
#include "cyclic.h"
void SendPayload(uint8_t port);
void sendCounter(void);
void checkSendQueues(void); void checkSendQueues(void);
void flushQueues(); void flushQueues();

View File

@ -1,34 +0,0 @@
#ifndef _SPISEND_H
#define _SPISEND_H
#include "globals.h"
#include "spi.h"
extern TaskHandle_t SpiTask;
extern QueueHandle_t SPISendQueue;
/*
* Process data in SPI send queue
*/
void spi_loop(void *pvParameters);
/*
* initialize local SPI wire interface
*/
void hal_spi_init();
/*
* Perform SPI write transaction on local SPI wire interface
* - write the command byte 'cmd'
* - write 'len' bytes out of 'buf'
*/
void hal_spi_write(uint8_t cmd, const uint8_t* buf, int len);
/*
* Perform SPI read transaction on local SPI wire interface
* - read the command byte 'cmd'
* - read 'len' bytes into 'buf'
*/
void hal_spi_read(uint8_t cmd, uint8_t* buf, int len);
#endif

36
include/spislave.h Normal file
View File

@ -0,0 +1,36 @@
/*
//////////////////////// ESP32-Paxcounter \\\\\\\\\\\\\\\\\\\\\\\\\\
Copyright 2018 Christian Ambach <christian.ambach@deutschebahn.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
NOTICE:
Parts of the source files in this repository are made available under different
licenses. Refer to LICENSE.txt file in repository for more details.
*/
#ifndef _SPISLAVE_H
#define _SPISLAVE_H
#include "globals.h"
esp_err_t spi_init();
void spi_enqueuedata(MessageBuffer_t *message);
void spi_queuereset();
void spi_housekeeping();
#endif // _SPISLAVE_H

View File

@ -41,7 +41,7 @@ class UpdateClass {
Call this to check the space needed for the update Call this to check the space needed for the update
Will return false if there is not enough space Will return false if there is not enough space
*/ */
bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH); bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW);
/* /*
Writes a buffer to the flash and increments the address Writes a buffer to the flash and increments the address
@ -174,8 +174,11 @@ class UpdateClass {
String _target_md5; String _target_md5;
MD5Builder _md5; MD5Builder _md5;
int _ledPin;
uint8_t _ledOn;
}; };
extern UpdateClass Update; extern UpdateClass Update;
#endif #endif

View File

@ -1,5 +1,4 @@
Arduino-LMIC library # Arduino-LMIC library
====================
This repository contains the IBM LMIC (LoraMAC-in-C) library, slightly This repository contains the IBM LMIC (LoraMAC-in-C) library, slightly
modified to run in the Arduino environment, allowing using the SX1272, modified to run in the Arduino environment, allowing using the SX1272,
@ -25,56 +24,56 @@ requires C99 mode to be enabled by default.
We strongly recommend updating using VS Code, the markdown-toc extension and the We strongly recommend updating using VS Code, the markdown-toc extension and the
bierner.markdown-preview-github-styles extension. bierner.markdown-preview-github-styles extension.
--> -->
<!-- TOC depthFrom:1 --> <!-- TOC depthFrom:2 -->
- [Installing](#installing) - [Installing](#installing)
- [Features](#features) - [Features](#features)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Selecting the LoRaWAN Region Configuration](#selecting-the-lorawan-region-configuration) - [Selecting the LoRaWAN Region Configuration](#selecting-the-lorawan-region-configuration)
- [eu868, as923, in866](#eu868-as923-in866) - [eu868, as923, in866](#eu868-as923-in866)
- [us915, au921](#us915-au921) - [us915, au921](#us915-au921)
- [Selecting the target radio transceiver](#selecting-the-target-radio-transceiver) - [Selecting the target radio transceiver](#selecting-the-target-radio-transceiver)
- [Controlling use of interrupts](#controlling-use-of-interrupts) - [Controlling use of interrupts](#controlling-use-of-interrupts)
- [Disabling PING](#disabling-ping) - [Disabling PING](#disabling-ping)
- [Disabling Beacons](#disabling-beacons) - [Disabling Beacons](#disabling-beacons)
- [Rarely changed variables](#rarely-changed-variables) - [Rarely changed variables](#rarely-changed-variables)
- [Changing debug output](#changing-debug-output) - [Changing debug output](#changing-debug-output)
- [Getting debug from the RF library](#getting-debug-from-the-rf-library) - [Getting debug from the RF library](#getting-debug-from-the-rf-library)
- [Selecting the AES library](#selecting-the-aes-library) - [Selecting the AES library](#selecting-the-aes-library)
- [Defining the OS Tick Frequency](#defining-the-os-tick-frequency) - [Defining the OS Tick Frequency](#defining-the-os-tick-frequency)
- [Setting the SPI-bus frequency](#setting-the-spi-bus-frequency) - [Setting the SPI-bus frequency](#setting-the-spi-bus-frequency)
- [Changing handling of runtime assertion failures](#changing-handling-of-runtime-assertion-failures) - [Changing handling of runtime assertion failures](#changing-handling-of-runtime-assertion-failures)
- [Disabling JOIN](#disabling-join) - [Disabling JOIN](#disabling-join)
- [Disabling Class A MAC commands](#disabling-class-a-mac-commands) - [Disabling Class A MAC commands](#disabling-class-a-mac-commands)
- [Disabling Class B MAC commands](#disabling-class-b-mac-commands) - [Disabling Class B MAC commands](#disabling-class-b-mac-commands)
- [Special purpose](#special-purpose) - [Special purpose](#special-purpose)
- [Supported hardware](#supported-hardware) - [Supported hardware](#supported-hardware)
- [Connections](#connections) - [Connections](#connections)
- [Power](#power) - [Power](#power)
- [SPI](#spi) - [SPI](#spi)
- [DIO pins](#dio-pins) - [DIO pins](#dio-pins)
- [Reset](#reset) - [Reset](#reset)
- [RXTX](#rxtx) - [RXTX](#rxtx)
- [RXTX Polarity](#rxtx-polarity) - [RXTX Polarity](#rxtx-polarity)
- [Pin mapping](#pin-mapping) - [Pin mapping](#pin-mapping)
- [Adafruit Feather M0 LoRa](#adafruit-feather-m0-lora) - [Adafruit Feather M0 LoRa](#adafruit-feather-m0-lora)
- [Adafruit Feather 32u4 LoRa](#adafruit-feather-32u4-lora) - [Adafruit Feather 32u4 LoRa](#adafruit-feather-32u4-lora)
- [LoRa Nexus by Ideetron](#lora-nexus-by-ideetron) - [LoRa Nexus by Ideetron](#lora-nexus-by-ideetron)
- [MCCI Catena 4450/4460](#mcci-catena-44504460) - [MCCI Catena 4450/4460](#mcci-catena-44504460)
- [MCCI Catena 4551](#mcci-catena-4551) - [MCCI Catena 4551](#mcci-catena-4551)
- [Example Sketches](#example-sketches) - [Example Sketches](#example-sketches)
- [Timing](#timing) - [Timing](#timing)
- [`LMIC_setClockError()`](#lmic_setclockerror) - [`LMIC_setClockError()`](#lmic_setclockerror)
- [Downlink datarate](#downlink-datarate) - [Downlink datarate](#downlink-datarate)
- [Encoding Utilities](#encoding-utilities) - [Encoding Utilities](#encoding-utilities)
- [sflt16](#sflt16) - [sflt16](#sflt16)
- [JavaScript decoder](#javascript-decoder) - [JavaScript decoder](#javascript-decoder)
- [uflt16](#uflt16) - [uflt16](#uflt16)
- [JavaScript decoder](#javascript-decoder-1) - [JavaScript decoder](#javascript-decoder-1)
- [sflt12](#sflt12) - [sflt12](#sflt12)
- [JavaScript decoder](#javascript-decoder-2) - [JavaScript decoder](#javascript-decoder-2)
- [uflt12](#uflt12) - [uflt12](#uflt12)
- [JavaScript decoder](#javascript-decoder-3) - [JavaScript decoder](#javascript-decoder-3)
- [Release History](#release-history) - [Release History](#release-history)
- [Contributions](#contributions) - [Contributions](#contributions)
- [Trademark Acknowledgements](#trademark-acknowledgements) - [Trademark Acknowledgements](#trademark-acknowledgements)
@ -86,12 +85,12 @@ requires C99 mode to be enabled by default.
To install this library: To install this library:
- install it using the Arduino Library manager ("Sketch" -> "Include - install it using the Arduino Library manager ("Sketch" -> "Include
Library" -> "Manage Libraries..."), or Library" -> "Manage Libraries..."), or
- download a zipfile from github using the "Download ZIP" button and - download a zipfile from github using the "Download ZIP" button and
install it using the IDE ("Sketch" -> "Include Library" -> "Add .ZIP install it using the IDE ("Sketch" -> "Include Library" -> "Add .ZIP
Library..." Library..."
- clone this git repository into your sketchbook/libraries folder. - clone this git repository into your sketchbook/libraries folder.
For more info, see https://www.arduino.cc/en/Guide/Libraries For more info, see https://www.arduino.cc/en/Guide/Libraries
@ -105,19 +104,19 @@ The library has only been tested with LoRaWAN 1.0.2 networks and does not have t
What certainly works: What certainly works:
- Sending packets uplink, taking into account duty cycling. - Sending packets uplink, taking into account duty cycling.
- Encryption and message integrity checking. - Encryption and message integrity checking.
- Receiving downlink packets in the RX2 window. - Receiving downlink packets in the RX2 window.
- Custom frequencies and datarate settings. - Custom frequencies and datarate settings.
- Over-the-air activation (OTAA / joining). - Over-the-air activation (OTAA / joining).
- Receiving downlink packets in the RX1 and RX2 windows. - Receiving downlink packets in the RX1 and RX2 windows.
- Some MAC command processing. - Some MAC command processing.
What has not been tested: What has not been tested:
- Receiving and processing all MAC commands. - Receiving and processing all MAC commands.
- Class B operation. - Class B operation.
- FSK has not been extensively tested. - FSK has not been extensively tested.
If you try one of these untested features and it works, be sure to let If you try one of these untested features and it works, be sure to let
us know (creating a github issue is probably the best way for that). us know (creating a github issue is probably the best way for that).
@ -131,7 +130,7 @@ directory is the only directory that contains files that you
should edit to match your project; we organize things this way should edit to match your project; we organize things this way
so that your local changes are more clearly separated from so that your local changes are more clearly separated from
the distribution files. The Arduino environment doesn't give the distribution files. The Arduino environment doesn't give
us a better way to do this. us a better way to do this, unless you change `BOARDS.txt`.
Unlike other ports of the LMIC code, in this port, you should not edit `src/lmic/config.h` to configure this package. Unlike other ports of the LMIC code, in this port, you should not edit `src/lmic/config.h` to configure this package.
@ -147,6 +146,7 @@ The library supports the following regions:
`-D CFG_us915` | `LMIC_REGION_us915` | 2 | 2.2 | US 902-928 MHz ISM `-D CFG_us915` | `LMIC_REGION_us915` | 2 | 2.2 | US 902-928 MHz ISM
`-D CFG_au921` | `LMIC_REGION_au921` | 5 | 2.5 | Australia 915-928 MHz ISM `-D CFG_au921` | `LMIC_REGION_au921` | 5 | 2.5 | Australia 915-928 MHz ISM
`-D CFG_as923` | `LMIC_REGION_as923` | 7 | 2.7 | Asia 923 MHz ISM `-D CFG_as923` | `LMIC_REGION_as923` | 7 | 2.7 | Asia 923 MHz ISM
`-D CFG_as923jp` | `LMIC_REGION_as923` and `LMIC_COUNTRY_CODE_JP` | 7 | 2.7 | Asia 923 MHz ISM with Japan listen-before-talk (LBT) rules
`-D CFG_in866` | `LMIC_REGION_in866` | 9 | 2.9 | India 865-867 MHz ISM `-D CFG_in866` | `LMIC_REGION_in866` | 9 | 2.9 | India 865-867 MHz ISM
You should define exactly one of `CFG_...` variables. If you don't, You should define exactly one of `CFG_...` variables. If you don't,
@ -205,8 +205,7 @@ By default, PING support is included in the library.
If defined, removes all code needed for handling beacons. Removes the APIs `LMIC_enableTracking()` and `LMIC_disableTracking()`. If defined, removes all code needed for handling beacons. Removes the APIs `LMIC_enableTracking()` and `LMIC_disableTracking()`.
Class A devices don't support beacons, so defining `DISABLE_BEACONS` might be a good idea. Class A devices don't support beacons, so defining `DISABLE_BEACONS` might be a good idea.
### Rarely changed variables
### Rarely changed variables ###
The remaining variables are rarely used, but we list them here for completeness. The remaining variables are rarely used, but we list them here for completeness.
@ -1022,6 +1021,10 @@ function uflt122f(rawUflt12)
## Release History ## Release History
- Interim bug fixes: added a new API (`radio_irq_handler_v2()`), which allows the caller to provide the timestamp of the interrupt. This allows for more accurate timing, because the knowledge of interrupt overhead can be moved to a platform-specific layer ([#148](https://github.com/mcci-catena/arduino-lmic/issues/148)). Fixed compile issues on ESP32 ([#140](https://github.com/mcci-catena/arduino-lmic/issues/140) and [#153](https://github.com/mcci-catena/arduino-lmic/issues/150)). We added ESP32 and 32u4 as targets in CI testing. We switched CI testing to Arduino IDE 1.8.7.
Fixed issue [#161](https://github.com/mcci-catena/arduino-lmic/issues/161) selecting the Japan version of as923 using `CFG_as923jp` (selecting via `CFG_as923` and `LMIC_COUNTRY_CODE=LMIC_COUNTRY_CODE_JP` worked).
Fixed [#38](https://github.com/mcci-catena/arduino-lmic/issues/38) -- now any call to hal_init() will put the NSS line in the idle (high/inactive) state. As a side effect, RXTX is initialized, and RESET code changed to set value before transitioning state. Likely no net effect, but certainly more correct.
- V2.2.2 adds `ttn-abp-feather-us915-dht22.ino` example, and fixes some documentation typos. It also fixes encoding of the `Margin` field of the `DevStatusAns` MAC message ([#130](https://github.com/mcci-catena/arduino-lmic/issues/130)). This makes Arduino LMIC work with newtorks implemented with [LoraServer](https://www.loraserver.io/). - V2.2.2 adds `ttn-abp-feather-us915-dht22.ino` example, and fixes some documentation typos. It also fixes encoding of the `Margin` field of the `DevStatusAns` MAC message ([#130](https://github.com/mcci-catena/arduino-lmic/issues/130)). This makes Arduino LMIC work with newtorks implemented with [LoraServer](https://www.loraserver.io/).
- V2.2.1 corrects the value of `ARDUINO_LMIC_VERSION` ([#123](https://github.com/mcci-catena/arduino-lmic/issues/123)), allows ttn-otaa-feather-us915 example to compile for the Feather 32u4 LoRa ([#116](https://github.com/mcci-catena/arduino-lmic/issues/116)), and addresses documentation issues ([#122](https://github.com/mcci-catena/arduino-lmic/issues/122), [#120](https://github.com/mcci-catena/arduino-lmic/issues/120)). - V2.2.1 corrects the value of `ARDUINO_LMIC_VERSION` ([#123](https://github.com/mcci-catena/arduino-lmic/issues/123)), allows ttn-otaa-feather-us915 example to compile for the Feather 32u4 LoRa ([#116](https://github.com/mcci-catena/arduino-lmic/issues/116)), and addresses documentation issues ([#122](https://github.com/mcci-catena/arduino-lmic/issues/122), [#120](https://github.com/mcci-catena/arduino-lmic/issues/120)).

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

View File

@ -0,0 +1,36 @@
/*
Module: header_test.ino
Function:
Simple hello-world (and compile-test) app
Copyright notice and License:
See LICENSE file accompanying this project.
Author:
Terry Moore, MCCI Corporation April 2018
*/
#include <lmic.h>
# define STATIC_ASSERT(e) \
void STATIC_ASSERT__(int MCCIADK_C_ASSERT_x[(e) ? 1: -1])
STATIC_ASSERT(ARDUINO_LMIC_VERSION >= ARDUINO_LMIC_VERSION_CALC(2,1,5,0));
STATIC_ASSERT(ARDUINO_LMIC_VERSION_CALC(1,2,3,4) == 0x01020304);
STATIC_ASSERT(ARDUINO_LMIC_VERSION_GET_MAJOR(ARDUINO_LMIC_VERSION_CALC(1,2,3,4)) == 1);
STATIC_ASSERT(ARDUINO_LMIC_VERSION_GET_MINOR(ARDUINO_LMIC_VERSION_CALC(1,2,3,4)) == 2);
STATIC_ASSERT(ARDUINO_LMIC_VERSION_GET_PATCH(ARDUINO_LMIC_VERSION_CALC(1,2,3,4)) == 3);
STATIC_ASSERT(ARDUINO_LMIC_VERSION_GET_LOCAL(ARDUINO_LMIC_VERSION_CALC(1,2,3,4)) == 4);
void setup()
{
}
void loop()
{
}

View File

@ -0,0 +1,441 @@
/*
Module: raw-feather.ino
Function:
Slightly improved Raw test example, for Adafruit Feather M0 LoRa
Copyright notice and License:
See LICENSE file accompanying this project.
Author:
Matthijs Kooijman 2015
Terry Moore, MCCI Corporation April 2017
*/
/*******************************************************************************
* Copyright (c) 2015 Matthijs Kooijman
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example transmits data on hardcoded channel and receives data
* when not transmitting. Running this sketch on two nodes should allow
* them to communicate.
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <stdarg.h>
#include <stdio.h>
// we formerly would check this configuration; but now there is a flag,
// in the LMIC, LMIC.noRXIQinversion;
// if we set that during init, we get the same effect. If
// DISABLE_INVERT_IQ_ON_RX is defined, it means that LMIC.noRXIQinversion is
// treated as always set.
//
// #if !defined(DISABLE_INVERT_IQ_ON_RX)
// #error This example requires DISABLE_INVERT_IQ_ON_RX to be set. Update \
// lmic_project_config.h in arduino-lmic/project_config to set it.
// #endif
// How often to send a packet. Note that this sketch bypasses the normal
// LMIC duty cycle limiting, so when you change anything in this sketch
// (payload length, frequency, spreading factor), be sure to check if
// this interval should not also be increased.
// See this spreadsheet for an easy airtime and duty cycle calculator:
// https://docs.google.com/spreadsheets/d/1voGAtQAjC1qBmaVuP1ApNKs1ekgUjavHuVQIXyYSvNc
#define TX_INTERVAL 2000 // milliseconds
#define RX_RSSI_INTERVAL 100 // milliseconds
// Pin mapping for Adafruit Feather M0 LoRa, etc.
#if defined(ARDUINO_SAMD_FEATHER_M0)
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {3, 6, LMIC_UNUSED_PIN},
.rxtx_rx_active = 0,
.rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
.spi_freq = 8000000,
};
#elif defined(ARDUINO_AVR_FEATHER32U4)
// Pin mapping for Adafruit Feather 32u4 LoRa, etc.
// Just like Feather M0 LoRa, but uses SPI at 1MHz; and that's only
// because MCCI doesn't have a test board; probably higher frequencies
// will work.
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {3, 6, LMIC_UNUSED_PIN},
.rxtx_rx_active = 0,
.rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
.spi_freq = 1000000,
};
#elif defined(ARDUINO_CATENA_4551)
const lmic_pinmap lmic_pins = {
.nss = 7,
.rxtx = 29,
.rst = 8,
.dio = { 25, // DIO0 (IRQ) is D25
26, // DIO1 is D26
27, // DIO2 is D27
},
.rxtx_rx_active = 1,
.rssi_cal = 10,
.spi_freq = 8000000 // 8MHz
};
#else
# error "Unknown target"
#endif
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in arduino-lmoc/project_config/lmic_project_config.h,
// otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }
// this gets callled by the library but we choose not to display any info;
// and no action is required.
void onEvent (ev_t ev) {
}
extern "C" {
void lmic_printf(const char *fmt, ...);
};
void lmic_printf(const char *fmt, ...) {
if (! Serial.dtr())
return;
char buf[256];
va_list ap;
va_start(ap, fmt);
(void) vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
va_end(ap);
// in case we overflowed:
buf[sizeof(buf) - 1] = '\0';
if (Serial.dtr()) Serial.print(buf);
}
osjob_t txjob;
osjob_t timeoutjob;
static void tx_func (osjob_t* job);
// Transmit the given string and call the given function afterwards
void tx(const char *str, osjobcb_t func) {
// the radio is probably in RX mode; stop it.
os_radio(RADIO_RST);
// wait a bit so the radio can come out of RX mode
delay(1);
// prepare data
LMIC.dataLen = 0;
while (*str)
LMIC.frame[LMIC.dataLen++] = *str++;
// set completion function.
LMIC.osjob.func = func;
// start the transmission
os_radio(RADIO_TX);
Serial.println("TX");
}
// Enable rx mode and call func when a packet is received
void rx(osjobcb_t func) {
LMIC.osjob.func = func;
LMIC.rxtime = os_getTime(); // RX _now_
// Enable "continuous" RX (e.g. without a timeout, still stops after
// receiving a packet)
os_radio(RADIO_RXON);
Serial.println("RX");
}
static void rxtimeout_func(osjob_t *job) {
digitalWrite(LED_BUILTIN, LOW); // off
}
static void rx_func (osjob_t* job) {
// Blink once to confirm reception and then keep the led on
digitalWrite(LED_BUILTIN, LOW); // off
delay(10);
digitalWrite(LED_BUILTIN, HIGH); // on
// Timeout RX (i.e. update led status) after 3 periods without RX
os_setTimedCallback(&timeoutjob, os_getTime() + ms2osticks(3*TX_INTERVAL), rxtimeout_func);
// Reschedule TX so that it should not collide with the other side's
// next TX
os_setTimedCallback(&txjob, os_getTime() + ms2osticks(TX_INTERVAL/2), tx_func);
Serial.print("Got ");
Serial.print(LMIC.dataLen);
Serial.println(" bytes");
Serial.write(LMIC.frame, LMIC.dataLen);
Serial.println();
// Restart RX
rx(rx_func);
}
static void txdone_func (osjob_t* job) {
rx(rx_func);
}
// log text to USART and toggle LED
static void tx_func (osjob_t* job) {
// say hello
tx("Hello, world!", txdone_func);
// reschedule job every TX_INTERVAL (plus a bit of random to prevent
// systematic collisions), unless packets are received, then rx_func
// will reschedule at half this time.
os_setTimedCallback(job, os_getTime() + ms2osticks(TX_INTERVAL + random(500)), tx_func);
}
// application entry point
void setup() {
// delay(3000) makes recovery from botched images much easier, as it
// gives the host time to break in to start a download. Without it,
// you get to the crash before the host can break in.
delay(3000);
// even after the delay, we wait for the host to open the port. operator
// bool(Serial) just checks dtr(), and it tosses in a 10ms delay.
while(! Serial.dtr())
/* wait for the PC */;
Serial.begin(115200);
Serial.println("Starting");
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
pinMode(LED_BUILTIN, OUTPUT);
// initialize runtime env
os_init();
// Set up these settings once, and use them for both TX and RX
#ifdef ARDUINO_ARCH_STM32
LMIC_setClockError(10*65536/100);
#endif
#if defined(CFG_eu868)
// Use a frequency in the g3 which allows 10% duty cycling.
LMIC.freq = 869525000;
// Use a medium spread factor. This can be increased up to SF12 for
// better range, but then, the interval should be (significantly)
// raised to comply with duty cycle limits as well.
LMIC.datarate = DR_SF9;
// Maximum TX power
LMIC.txpow = 27;
#elif defined(CFG_us915)
// make it easier for test, by pull the parameters up to the top of the
// block. Ideally, we'd use the serial port to drive this; or have
// a voting protocol where one side is elected the controller and
// guides the responder through all the channels, powers, ramps
// the transmit power from min to max, and measures the RSSI and SNR.
// Even more amazing would be a scheme where the controller could
// handle multiple nodes; in that case we'd have a way to do
// production test and qualification. However, using an RWC5020A
// is a much better use of development time.
// set fDownlink true to use a downlink channel; false
// to use an uplink channel. Generally speaking, uplink
// is more interesting, because you can prove that gateways
// *should* be able to hear you.
const static bool fDownlink = false;
// the downlink channel to be used.
const static uint8_t kDownlinkChannel = 3;
// the uplink channel to be used.
const static uint8_t kUplinkChannel = 8 + 3;
// this is automatically set to the proper bandwidth in kHz,
// based on the selected channel.
uint32_t uBandwidth;
if (! fDownlink)
{
if (kUplinkChannel < 64)
{
LMIC.freq = US915_125kHz_UPFBASE +
kUplinkChannel * US915_125kHz_UPFSTEP;
uBandwidth = 125;
}
else
{
LMIC.freq = US915_500kHz_UPFBASE +
(kUplinkChannel - 64) * US915_500kHz_UPFSTEP;
uBandwidth = 500;
}
}
else
{
// downlink channel
LMIC.freq = US915_500kHz_DNFBASE +
kDownlinkChannel * US915_500kHz_DNFSTEP;
uBandwidth = 500;
}
// Use a suitable spreading factor
if (uBandwidth < 500)
LMIC.datarate = US915_DR_SF7; // DR4
else
LMIC.datarate = US915_DR_SF12CR; // DR8
// default tx power for US: 21 dBm
LMIC.txpow = 21;
#elif defined(CFG_au921)
// make it easier for test, by pull the parameters up to the top of the
// block. Ideally, we'd use the serial port to drive this; or have
// a voting protocol where one side is elected the controller and
// guides the responder through all the channels, powers, ramps
// the transmit power from min to max, and measures the RSSI and SNR.
// Even more amazing would be a scheme where the controller could
// handle multiple nodes; in that case we'd have a way to do
// production test and qualification. However, using an RWC5020A
// is a much better use of development time.
// set fDownlink true to use a downlink channel; false
// to use an uplink channel. Generally speaking, uplink
// is more interesting, because you can prove that gateways
// *should* be able to hear you.
const static bool fDownlink = false;
// the downlink channel to be used.
const static uint8_t kDownlinkChannel = 3;
// the uplink channel to be used.
const static uint8_t kUplinkChannel = 8 + 3;
// this is automatically set to the proper bandwidth in kHz,
// based on the selected channel.
uint32_t uBandwidth;
if (! fDownlink)
{
if (kUplinkChannel < 64)
{
LMIC.freq = AU921_125kHz_UPFBASE +
kUplinkChannel * AU921_125kHz_UPFSTEP;
uBandwidth = 125;
}
else
{
LMIC.freq = AU921_500kHz_UPFBASE +
(kUplinkChannel - 64) * AU921_500kHz_UPFSTEP;
uBandwidth = 500;
}
}
else
{
// downlink channel
LMIC.freq = AU921_500kHz_DNFBASE +
kDownlinkChannel * AU921_500kHz_DNFSTEP;
uBandwidth = 500;
}
// Use a suitable spreading factor
if (uBandwidth < 500)
LMIC.datarate = AU921_DR_SF7; // DR4
else
LMIC.datarate = AU921_DR_SF12CR; // DR8
// default tx power for AU: 30 dBm
LMIC.txpow = 30;
#elif defined(CFG_as923)
// make it easier for test, by pull the parameters up to the top of the
// block. Ideally, we'd use the serial port to drive this; or have
// a voting protocol where one side is elected the controller and
// guides the responder through all the channels, powers, ramps
// the transmit power from min to max, and measures the RSSI and SNR.
// Even more amazing would be a scheme where the controller could
// handle multiple nodes; in that case we'd have a way to do
// production test and qualification. However, using an RWC5020A
// is a much better use of development time.
const static uint8_t kChannel = 0;
uint32_t uBandwidth;
LMIC.freq = AS923_F1 + kChannel * 200000;
uBandwidth = 125;
// Use a suitable spreading factor
if (uBandwidth == 125)
LMIC.datarate = AS923_DR_SF7; // DR7
else
LMIC.datarate = AS923_DR_SF7B; // DR8
// default tx power for AS: 21 dBm
LMIC.txpow = 16;
if (LMIC_COUNTRY_CODE == LMIC_COUNTRY_CODE_JP)
{
LMIC.lbt_ticks = us2osticks(AS923JP_LBT_US);
LMIC.lbt_dbmax = AS923JP_LBT_DB_MAX;
}
#elif defined(CFG_in866)
// make it easier for test, by pull the parameters up to the top of the
// block. Ideally, we'd use the serial port to drive this; or have
// a voting protocol where one side is elected the controller and
// guides the responder through all the channels, powers, ramps
// the transmit power from min to max, and measures the RSSI and SNR.
// Even more amazing would be a scheme where the controller could
// handle multiple nodes; in that case we'd have a way to do
// production test and qualification. However, using an RWC5020A
// is a much better use of development time.
const static uint8_t kChannel = 0;
uint32_t uBandwidth;
LMIC.freq = IN866_F1 + kChannel * 200000;
uBandwidth = 125;
LMIC.datarate = IN866_DR_SF7; // DR7
// default tx power for IN: 30 dBm
LMIC.txpow = IN866_TX_EIRP_MAX_DBM;
#else
# error Unsupported LMIC regional configuration.
#endif
// disable RX IQ inversion
LMIC.noRXIQinversion = true;
// This sets CR 4/5, BW125 (except for EU/AS923 DR_SF7B, which uses BW250)
LMIC.rps = updr2rps(LMIC.datarate);
Serial.print("Frequency: "); Serial.print(LMIC.freq / 1000000);
Serial.print("."); Serial.print((LMIC.freq / 100000) % 10);
Serial.print("MHz");
Serial.print(" LMIC.datarate: "); Serial.print(LMIC.datarate);
Serial.print(" LMIC.txpow: "); Serial.println(LMIC.txpow);
Serial.println("Started");
Serial.flush();
// setup initial job
os_setCallback(&txjob, tx_func);
}
void loop() {
// execute scheduled jobs and events
os_runloop_once();
}

View File

@ -0,0 +1,175 @@
/*******************************************************************************
* Copyright (c) 2015 Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI Corporation
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example transmits data on hardcoded channel and receives data
* when not transmitting. Running this sketch on two nodes should allow
* them to communicate.
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
// we formerly would check this configuration; but now there is a flag,
// in the LMIC, LMIC.noRXIQinversion;
// if we set that during init, we get the same effect. If
// DISABLE_INVERT_IQ_ON_RX is defined, it means that LMIC.noRXIQinversion is
// treated as always set.
//
// #if !defined(DISABLE_INVERT_IQ_ON_RX)
// #error This example requires DISABLE_INVERT_IQ_ON_RX to be set. Update \
// lmic_project_config.h in arduino-lmic/project_config to set it.
// #endif
// How often to send a packet. Note that this sketch bypasses the normal
// LMIC duty cycle limiting, so when you change anything in this sketch
// (payload length, frequency, spreading factor), be sure to check if
// this interval should not also be increased.
// See this spreadsheet for an easy airtime and duty cycle calculator:
// https://docs.google.com/spreadsheets/d/1voGAtQAjC1qBmaVuP1ApNKs1ekgUjavHuVQIXyYSvNc
#define TX_INTERVAL 2000
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = 6,
.rxtx = LMIC_UNUSED_PIN,
.rst = 5,
.dio = {2, 3, 4},
};
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in arduino-lmoc/project_config/lmic_project_config.h,
// otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }
void onEvent (ev_t ev) {
}
osjob_t txjob;
osjob_t timeoutjob;
static void tx_func (osjob_t* job);
// Transmit the given string and call the given function afterwards
void tx(const char *str, osjobcb_t func) {
os_radio(RADIO_RST); // Stop RX first
delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet
LMIC.dataLen = 0;
while (*str)
LMIC.frame[LMIC.dataLen++] = *str++;
LMIC.osjob.func = func;
os_radio(RADIO_TX);
Serial.println("TX");
}
// Enable rx mode and call func when a packet is received
void rx(osjobcb_t func) {
LMIC.osjob.func = func;
LMIC.rxtime = os_getTime(); // RX _now_
// Enable "continuous" RX (e.g. without a timeout, still stops after
// receiving a packet)
os_radio(RADIO_RXON);
Serial.println("RX");
}
static void rxtimeout_func(osjob_t *job) {
digitalWrite(LED_BUILTIN, LOW); // off
}
static void rx_func (osjob_t* job) {
// Blink once to confirm reception and then keep the led on
digitalWrite(LED_BUILTIN, LOW); // off
delay(10);
digitalWrite(LED_BUILTIN, HIGH); // on
// Timeout RX (i.e. update led status) after 3 periods without RX
os_setTimedCallback(&timeoutjob, os_getTime() + ms2osticks(3*TX_INTERVAL), rxtimeout_func);
// Reschedule TX so that it should not collide with the other side's
// next TX
os_setTimedCallback(&txjob, os_getTime() + ms2osticks(TX_INTERVAL/2), tx_func);
Serial.print("Got ");
Serial.print(LMIC.dataLen);
Serial.println(" bytes");
Serial.write(LMIC.frame, LMIC.dataLen);
Serial.println();
// Restart RX
rx(rx_func);
}
static void txdone_func (osjob_t* job) {
rx(rx_func);
}
// log text to USART and toggle LED
static void tx_func (osjob_t* job) {
// say hello
tx("Hello, world!", txdone_func);
// reschedule job every TX_INTERVAL (plus a bit of random to prevent
// systematic collisions), unless packets are received, then rx_func
// will reschedule at half this time.
os_setTimedCallback(job, os_getTime() + ms2osticks(TX_INTERVAL + random(500)), tx_func);
}
// application entry point
void setup() {
Serial.begin(115200);
Serial.println("Starting");
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
pinMode(LED_BUILTIN, OUTPUT);
// initialize runtime env
os_init();
// Set up these settings once, and use them for both TX and RX
#if defined(CFG_eu868)
// Use a frequency in the g3 which allows 10% duty cycling.
LMIC.freq = 869525000;
#elif defined(CFG_us915)
LMIC.freq = 902300000;
#else
error Region not supported!
#endif
// Maximum TX power
LMIC.txpow = 27;
// Use a medium spread factor. This can be increased up to SF12 for
// better range, but then the interval should be (significantly)
// lowered to comply with duty cycle limits as well.
LMIC.datarate = DR_SF9;
// This sets CR 4/5, BW125 (except for DR_SF7B, which uses BW250)
LMIC.rps = updr2rps(LMIC.datarate);
// disable RX IQ inversion
LMIC.noRXIQinversion = true;
Serial.println("Started");
Serial.flush();
// setup initial job
os_setCallback(&txjob, tx_func);
}
void loop() {
// execute scheduled jobs and events
os_runloop_once();
}

View File

@ -0,0 +1,266 @@
/*******************************************************************************
* The Things Network - ABP Feather
*
* Example of using an Adafruit Feather M0 and DHT22 with a
* single-channel TheThingsNetwork gateway.
*
* This uses ABP (Activation by Personalization), where session keys for
* communication would be assigned/generated by TTN and hard-coded on the device.
*
* Learn Guide: https://learn.adafruit.com/lora-pi
*
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
* Copyright (c) 2018 Brent Rubell, Adafruit Industries
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
// include the DHT22 Sensor Library
#include "DHT.h"
// DHT digital pin and sensor type
#define DHTPIN 10
#define DHTTYPE DHT22
//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif
// LoRaWAN NwkSKey, network session key
static const PROGMEM u1_t NWKSKEY[16] = { FILLMEIN };
// LoRaWAN AppSKey, application session key
static const u1_t PROGMEM APPSKEY[16] = { FILLMEIN };
// LoRaWAN end-device address (DevAddr)
// See http://thethingsnetwork.org/wiki/AddressSpace
// The library converts the address to network byte order as needed.
#ifndef COMPILE_REGRESSION_TEST
static const u4_t DEVADDR = 0xFILLMEIN;
#else
static const u4_t DEVADDR = 0;
#endif
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in arduino-lmic/project_config/lmic_project_config.h,
// otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }
// payload to send to TTN gateway
static uint8_t payload[5];
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 30;
// Pin mapping for Adafruit Feather M0 LoRa
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {3, 6, LMIC_UNUSED_PIN},
.rxtx_rx_active = 0,
.rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
.spi_freq = 8000000,
};
// init. DHT
DHT dht(DHTPIN, DHTTYPE);
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
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"));
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| 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;
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"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
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;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// read the temperature from the DHT22
float temperature = dht.readTemperature();
Serial.print("Temperature: "); Serial.print(temperature);
Serial.println(" *C");
// adjust for the f2sflt16 range (-1 to 1)
temperature = temperature / 100;
// read the humidity from the DHT22
float rHumidity = dht.readHumidity();
Serial.print("%RH ");
Serial.println(rHumidity);
// adjust for the f2sflt16 range (-1 to 1)
rHumidity = rHumidity / 100;
// float -> int
// note: this uses the sflt16 datum (https://github.com/mcci-catena/arduino-lmic#sflt16)
uint16_t payloadTemp = LMIC_f2sflt16(temperature);
// int -> bytes
byte tempLow = lowByte(payloadTemp);
byte tempHigh = highByte(payloadTemp);
// place the bytes into the payload
payload[0] = tempLow;
payload[1] = tempHigh;
// float -> int
uint16_t payloadHumid = LMIC_f2sflt16(rHumidity);
// int -> bytes
byte humidLow = lowByte(payloadHumid);
byte humidHigh = highByte(payloadHumid);
payload[2] = humidLow;
payload[3] = humidHigh;
// prepare upstream data transmission at the next possible time.
// transmit on port 1 (the first parameter); you can use any value from 1 to 223 (others are reserved).
// don't request an ack (the last parameter, if not zero, requests an ack from the network).
// Remember, acks consume a lot of network resources; don't ask for an ack unless you really need it.
LMIC_setTxData2(1, payload, sizeof(payload)-1, 0);
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
delay(5000);
while (!Serial);
Serial.begin(115200);
delay(100);
Serial.println(F("Starting"));
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Set static session parameters. Instead of dynamically establishing a session
// by joining the network, precomputed session parameters are be provided.
// On AVR, these values are stored in flash and only copied to RAM
// once. Copy them to a temporary buffer here, LMIC_setSession will
// copy them into a buffer of its own again.
uint8_t appskey[sizeof(APPSKEY)];
uint8_t nwkskey[sizeof(NWKSKEY)];
memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
LMIC_setSession (0x13, DEVADDR, nwkskey, appskey);
// We'll disable all 72 channels used by TTN
for (int c = 0; c < 72; c++){
LMIC_disableChannel(c);
}
// We'll only enable Channel 16 (905.5Mhz) since we're transmitting on a single-channel
LMIC_enableChannel(16);
// Disable link check validation
LMIC_setLinkCheckMode(0);
// TTN uses SF9 for its RX2 window.
LMIC.dn2Dr = DR_SF9;
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
LMIC_setDrTxpow(DR_SF7,14);
// Start job
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}

View File

@ -0,0 +1,276 @@
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses ABP (Activation-by-personalisation), where a DevAddr and
* Session keys are preconfigured (unlike OTAA, where a DevEUI and
* application key is configured, while the DevAddr and session keys are
* assigned/generated in the over-the-air-activation procedure).
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
*
* To use this sketch, first register your application and device with
* the things network, to set or generate a DevAddr, NwkSKey and
* AppSKey. Each device should have their own unique values for these
* fields.
*
* Do not forget to define the radio type correctly in
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
*
*******************************************************************************/
// References:
// [feather] adafruit-feather-m0-radio-with-lora-module.pdf
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif
// LoRaWAN NwkSKey, network session key
static const PROGMEM u1_t NWKSKEY[16] = { FILLMEIN };
// LoRaWAN AppSKey, application session key
static const u1_t PROGMEM APPSKEY[16] = { FILLMEIN };
// LoRaWAN end-device address (DevAddr)
// See http://thethingsnetwork.org/wiki/AddressSpace
// The library converts the address to network byte order as needed.
static const u4_t DEVADDR = FILLMEIN ; // <-- Change this address for every node!
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in arduino-lmic/project_config/lmic_project_config.h,
// otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
// Pin mapping
// Adapted for Feather M0 per p.10 of [feather]
const lmic_pinmap lmic_pins = {
.nss = 8, // chip select on feather (rf95module) CS
.rxtx = LMIC_UNUSED_PIN,
.rst = 4, // reset pin
.dio = {6, 5, LMIC_UNUSED_PIN}, // assumes external jumpers [feather_lora_jumper]
// DIO1 is on JP1-1: is io1 - we connect to GPO6
// DIO1 is on JP5-3: is D2 - we connect to GPO5
};
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
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"));
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| 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;
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"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
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;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
// pinMode(13, OUTPUT);
while (!Serial); // wait for Serial to be initialized
Serial.begin(115200);
delay(100); // per sample code on RF_95 test
Serial.println(F("Starting"));
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Set static session parameters. Instead of dynamically establishing a session
// by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
// On AVR, these values are stored in flash and only copied to RAM
// once. Copy them to a temporary buffer here, LMIC_setSession will
// copy them into a buffer of its own again.
uint8_t appskey[sizeof(APPSKEY)];
uint8_t nwkskey[sizeof(NWKSKEY)];
memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
LMIC_setSession (0x13, DEVADDR, nwkskey, appskey);
#else
// If not running an AVR with PROGMEM, just use the arrays directly
LMIC_setSession (0x13, DEVADDR, NWKSKEY, APPSKEY);
#endif
#if defined(CFG_eu868)
// Set up the channels used by the Things Network, which corresponds
// to the defaults of most gateways. Without this, only three base
// channels from the LoRaWAN specification are used, which certainly
// works, so it is good for debugging, but can overload those
// frequencies, so be sure to configure the full frequency range of
// your network here (unless your network autoconfigures them).
// Setting up channels should happen after LMIC_setSession, as that
// configures the minimal channel set.
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
// devices' ping slots. LMIC does not have an easy way to define set this
// frequency and support for class B is spotty and untested, so this
// frequency is not configured here.
#elif defined(CFG_us915)
// NA-US channels 0-71 are configured automatically
// but only one group of 8 should (a subband) should be active
// TTN recommends the second sub band, 1 in a zero based count.
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
LMIC_selectSubBand(1);
#endif
// Disable link check validation
LMIC_setLinkCheckMode(0);
// TTN uses SF9 for its RX2 window.
LMIC.dn2Dr = DR_SF9;
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
LMIC_setDrTxpow(DR_SF7,14);
// Start job
do_send(&sendjob);
}
void loop() {
unsigned long now;
now = millis();
if ((now & 512) != 0) {
digitalWrite(13, HIGH);
}
else {
digitalWrite(13, LOW);
}
os_runloop_once();
}

View File

@ -0,0 +1,274 @@
/*******************************************************************************
* The Things Network - Sensor Data Example
*
* Example of sending a valid LoRaWAN packet with DHT22 temperature and
* humidity data to The Things Networ using a Feather M0 LoRa.
*
* Learn Guide: https://learn.adafruit.com/the-things-network-for-feather
*
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
* Copyright (c) 2018 Brent Rubell, Adafruit Industries
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
// include the DHT22 Sensor Library
#include "DHT.h"
// DHT digital pin and sensor type
#define DHTPIN 10
#define DHTTYPE DHT22
//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
#define FILLMEIN 0
#else
#warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
#define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8] = { FILLMEIN };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8] = { FILLMEIN };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from the TTN console can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { FILLMEIN };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
// payload to send to TTN gateway
static uint8_t payload[5];
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 30;
// Pin mapping for Adafruit Feather M0 LoRa
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {3, 6, LMIC_UNUSED_PIN},
.rxtx_rx_active = 0,
.rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
.spi_freq = 8000000,
};
// init. DHT
DHT dht(DHTPIN, DHTTYPE);
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
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"));
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("netid: ");
Serial.println(netid, DEC);
Serial.print("devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("artKey: ");
for (int i=0; i<sizeof(artKey); ++i) {
if (i != 0)
Serial.print("-");
Serial.print(artKey[i], HEX);
}
Serial.println("");
Serial.print("nwkKey: ");
for (int i=0; i<sizeof(nwkKey); ++i) {
if (i != 0)
Serial.print("-");
Serial.print(nwkKey[i], HEX);
}
Serial.println("");
}
// Disable link check validation (automatically enabled
// during join, but because slow data rates change max TX
// size, we don't use it in this example.
LMIC_setLinkCheckMode(0);
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| 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"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
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;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// read the temperature from the DHT22
float temperature = dht.readTemperature();
Serial.print("Temperature: "); Serial.print(temperature);
Serial.println(" *C");
// adjust for the f2sflt16 range (-1 to 1)
temperature = temperature / 100;
// read the humidity from the DHT22
float rHumidity = dht.readHumidity();
Serial.print("%RH ");
Serial.println(rHumidity);
// adjust for the f2sflt16 range (-1 to 1)
rHumidity = rHumidity / 100;
// float -> int
// note: this uses the sflt16 datum (https://github.com/mcci-catena/arduino-lmic#sflt16)
uint16_t payloadTemp = LMIC_f2sflt16(temperature);
// int -> bytes
byte tempLow = lowByte(payloadTemp);
byte tempHigh = highByte(payloadTemp);
// place the bytes into the payload
payload[0] = tempLow;
payload[1] = tempHigh;
// float -> int
uint16_t payloadHumid = LMIC_f2sflt16(rHumidity);
// int -> bytes
byte humidLow = lowByte(payloadHumid);
byte humidHigh = highByte(payloadHumid);
payload[2] = humidLow;
payload[3] = humidHigh;
// prepare upstream data transmission at the next possible time.
// transmit on port 1 (the first parameter); you can use any value from 1 to 223 (others are reserved).
// don't request an ack (the last parameter, if not zero, requests an ack from the network).
// Remember, acks consume a lot of network resources; don't ask for an ack unless you really need it.
LMIC_setTxData2(1, payload, sizeof(payload)-1, 0);
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
delay(5000);
while (! Serial);
Serial.begin(9600);
Serial.println(F("Starting"));
dht.begin();
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Disable link-check mode and ADR, because ADR tends to complicate testing.
LMIC_setLinkCheckMode(0);
// Set the data rate to Spreading Factor 7. This is the fastest supported rate for 125 kHz channels, and it
// minimizes air time and battery power. Set the transmission power to 14 dBi (25 mW).
LMIC_setDrTxpow(DR_SF7,14);
// in the US, with TTN, it saves join time if we start on subband 1 (channels 8-15). This will
// get overridden after the join by parameters from the network. If working with other
// networks or in other regions, this will need to be changed.
LMIC_selectSubBand(1);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
// we call the LMIC's runloop processor. This will cause things to happen based on events and time. One
// of the things that will happen is callbacks for transmission complete or received messages. We also
// use this loop to queue periodic data transmissions. You can put other things here in the `loop()` routine,
// but beware that LoRaWAN timing is pretty tight, so if you do more than a few milliseconds of work, you
// will want to call `os_runloop_once()` every so often, to keep the radio running.
os_runloop_once();
}

View File

@ -0,0 +1,274 @@
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network. It's pre-configured for the Adafruit
* Feather M0 LoRa.
*
* This uses OTAA (Over-the-air activation), where where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
* Do not forget to define the radio type correctly in
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
*
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8]= { FILLMEIN };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]= { FILLMEIN };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from the TTN console can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { FILLMEIN };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
// Pin mapping
#if defined(ARDUINO_SAMD_FEATHER_M0)
// Pin mapping for Adafruit Feather M0 LoRa, etc.
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {3, 6, LMIC_UNUSED_PIN},
.rxtx_rx_active = 0,
.rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
.spi_freq = 8000000,
};
#elif defined(ARDUINO_AVR_FEATHER32U4)
// Pin mapping for Adafruit Feather 32u4 LoRa, etc.
// Just like Feather M0 LoRa, but uses SPI at 1MHz; and that's only
// because MCCI doesn't have a test board; probably higher frequencies
// will work.
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {3, 6, LMIC_UNUSED_PIN},
.rxtx_rx_active = 0,
.rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
.spi_freq = 1000000,
};
#elif defined(ARDUINO_CATENA_4551)
// Pin mapping for Murata module / Catena 4551
const lmic_pinmap lmic_pins = {
.nss = 7,
.rxtx = 29,
.rst = 8,
.dio = { 25, // DIO0 (IRQ) is D25
26, // DIO1 is D26
27, // DIO2 is D27
},
.rxtx_rx_active = 1,
.rssi_cal = 10,
.spi_freq = 8000000 // 8MHz
};
#else
# error "Unknown target"
#endif
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
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"));
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("netid: ");
Serial.println(netid, DEC);
Serial.print("devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("artKey: ");
for (int i=0; i<sizeof(artKey); ++i) {
if (i != 0)
Serial.print("-");
Serial.print(artKey[i], HEX);
}
Serial.println("");
Serial.print("nwkKey: ");
for (int i=0; i<sizeof(nwkKey); ++i) {
if (i != 0)
Serial.print("-");
Serial.print(nwkKey[i], HEX);
}
Serial.println("");
}
// Disable link check validation (automatically enabled
// during join, but because slow data rates change max TX
// size, we don't use it in this example.
LMIC_setLinkCheckMode(0);
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| 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"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
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;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
delay(5000);
while (! Serial)
;
Serial.begin(9600);
Serial.println(F("Starting"));
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
LMIC_setLinkCheckMode(0);
LMIC_setDrTxpow(DR_SF7,14);
LMIC_selectSubBand(1);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}

View File

@ -0,0 +1,293 @@
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses OTAA (Over-the-air activation), where where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
* Do not forget to define the radio type correctly in config.h.
*
*******************************************************************************/
#include <Time.h>
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8]={ FILLMEIN };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ FILLMEIN };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { FILLMEIN };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = 6,
.rxtx = LMIC_UNUSED_PIN,
.rst = 5,
.dio = {2, 3, 4},
};
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
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"));
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("netid: ");
Serial.println(netid, DEC);
Serial.print("devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("artKey: ");
for (int i=0; i<sizeof(artKey); ++i) {
Serial.print(artKey[i], HEX);
}
Serial.println("");
Serial.print("nwkKey: ");
for (int i=0; i<sizeof(nwkKey); ++i) {
Serial.print(nwkKey[i], HEX);
}
Serial.println("");
}
// Disable link check validation (automatically enabled
// during join, but because slow data rates change max TX
// size, we don't use it in this example.
LMIC_setLinkCheckMode(0);
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| 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;
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.print(F("Received "));
Serial.print(LMIC.dataLen);
Serial.println(F(" bytes of payload"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
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;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
uint32_t userUTCTime; // Seconds since the UTC epoch
// Utility function for digital clock display: prints preceding colon and
// leading 0
void printDigits(int digits) {
Serial.print(':');
if (digits < 10) Serial.print('0');
Serial.print(digits);
}
void user_request_network_time_callback(void *pVoidUserUTCTime, int flagSuccess) {
// Explicit conversion from void* to uint32_t* to avoid compiler errors
uint32_t *pUserUTCTime = (uint32_t *) pVoidUserUTCTime;
// A struct that will be populated by LMIC_getNetworkTimeReference.
// It contains the following fields:
// - tLocal: the value returned by os_GetTime() when the time
// request was sent to the gateway, and
// - tNetwork: the seconds between the GPS epoch and the time
// the gateway received the time request
lmic_time_reference_t lmicTimeReference;
if (flagSuccess != 1) {
Serial.println(F("USER CALLBACK: Not a success"));
return;
}
// Populate "lmic_time_reference"
flagSuccess = LMIC_getNetworkTimeReference(&lmicTimeReference);
if (flagSuccess != 1) {
Serial.println(F("USER CALLBACK: LMIC_getNetworkTimeReference didn't succeed"));
return;
}
// Update userUTCTime, considering the difference between the GPS and UTC
// epoch, and the leap seconds
*pUserUTCTime = lmicTimeReference.tNetwork + 315964800;
// Add the delay between the instant the time was transmitted and
// the current time
// Current time, in ticks
ostime_t ticksNow = os_getTime();
// Time when the request was sent, in ticks
ostime_t ticksRequestSent = lmicTimeReference.tLocal;
uint32_t requestDelaySec = osticks2ms(ticksNow - ticksRequestSent) / 1000;
*pUserUTCTime += requestDelaySec;
// Update the system time with the time read from the network
setTime(*pUserUTCTime);
Serial.print(F("The current UTC time is: "));
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(' ');
Serial.print(day());
Serial.print('/');
Serial.print(month());
Serial.print('/');
Serial.print(year());
Serial.println();
}
void do_send(osjob_t* j) {
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Schedule a network time request at the next possible time
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
Serial.begin(9600);
Serial.println(F("Starting"));
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}

View File

@ -0,0 +1,225 @@
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses OTAA (Over-the-air activation), where where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
* Do not forget to define the radio type correctly in
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
*
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8]={ FILLMEIN };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ FILLMEIN };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { FILLMEIN };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = 6,
.rxtx = LMIC_UNUSED_PIN,
.rst = 5,
.dio = {2, 3, 4},
};
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
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"));
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("netid: ");
Serial.println(netid, DEC);
Serial.print("devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("artKey: ");
for (int i=0; i<sizeof(artKey); ++i) {
Serial.print(artKey[i], HEX);
}
Serial.println("");
Serial.print("nwkKey: ");
for (int i=0; i<sizeof(nwkKey); ++i) {
Serial.print(nwkKey[i], HEX);
}
Serial.println("");
}
// Disable link check validation (automatically enabled
// during join, but because slow data rates change max TX
// size, we don't use it in this example.
LMIC_setLinkCheckMode(0);
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| 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;
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.print(F("Received "));
Serial.print(LMIC.dataLen);
Serial.println(F(" bytes of payload"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
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;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
Serial.begin(9600);
Serial.println(F("Starting"));
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}

View File

@ -0,0 +1,9 @@
// project-specific definitions
//#define CFG_eu868 1
#define CFG_us915 1
//#define CFG_au921 1
//#define CFG_as923 1
// #define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP /* for as923-JP */
//#define CFG_in866 1
#define CFG_sx1276_radio 1
//#define LMIC_USE_INTERRUPTS

View File

@ -85,7 +85,6 @@ static unsigned char AES_Sub_Byte(unsigned char Byte);
static void AES_Shift_Rows(); static void AES_Shift_Rows();
static void AES_Mix_Collums(); static void AES_Mix_Collums();
static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key); static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key);
static void Send_State();
/* /*
***************************************************************************************** *****************************************************************************************

View File

@ -33,11 +33,19 @@ static void hal_io_init () {
// Serial.print("dio[1]: "); Serial.println(plmic_pins->dio[1]); // Serial.print("dio[1]: "); Serial.println(plmic_pins->dio[1]);
// Serial.print("dio[2]: "); Serial.println(plmic_pins->dio[2]); // Serial.print("dio[2]: "); Serial.println(plmic_pins->dio[2]);
// initialize SPI chip select to high (it's active low)
digitalWrite(plmic_pins->nss, HIGH);
pinMode(plmic_pins->nss, OUTPUT); pinMode(plmic_pins->nss, OUTPUT);
if (plmic_pins->rxtx != LMIC_UNUSED_PIN)
if (plmic_pins->rxtx != LMIC_UNUSED_PIN) {
// initialize to RX
digitalWrite(plmic_pins->rxtx, LOW != plmic_pins->rxtx_rx_active);
pinMode(plmic_pins->rxtx, OUTPUT); pinMode(plmic_pins->rxtx, OUTPUT);
if (plmic_pins->rst != LMIC_UNUSED_PIN) }
pinMode(plmic_pins->rst, OUTPUT); if (plmic_pins->rst != LMIC_UNUSED_PIN) {
// initialize RST to floating
pinMode(plmic_pins->rst, INPUT);
}
hal_interrupt_init(); hal_interrupt_init();
} }
@ -54,8 +62,8 @@ void hal_pin_rst (u1_t val) {
return; return;
if(val == 0 || val == 1) { // drive pin if(val == 0 || val == 1) { // drive pin
pinMode(plmic_pins->rst, OUTPUT);
digitalWrite(plmic_pins->rst, val); digitalWrite(plmic_pins->rst, val);
pinMode(plmic_pins->rst, OUTPUT);
} else { // keep pin floating } else { // keep pin floating
pinMode(plmic_pins->rst, INPUT); pinMode(plmic_pins->rst, INPUT);
} }

View File

@ -171,4 +171,11 @@
# endif // defined(LMIC_DISABLE_DR_LEGACY) # endif // defined(LMIC_DISABLE_DR_LEGACY)
#endif // LMIC_DR_LEGACY #endif // LMIC_DR_LEGACY
// LMIC_ENABLE_DeviceTimeReq
// enable support for MCMD_DeviceTimeReq and MCMD_DeviceTimeAns
// this is always defined, and non-zero to enable it.
#if !defined(LMIC_ENABLE_DeviceTimeReq)
# define LMIC_ENABLE_DeviceTimeReq 0
#endif
#endif // _lmic_config_h_ #endif // _lmic_config_h_

View File

@ -47,8 +47,10 @@ static void startScan (void);
#endif #endif
static inline void initTxrxFlags(const char *func, u1_t mask) { static inline void initTxrxFlags(const char *func, u1_t mask) {
LMIC_DEBUG2_PARAMETER(func);
#if LMIC_DEBUG_LEVEL > 1 #if LMIC_DEBUG_LEVEL > 1
LMIC_DEBUG_PRINTF("%lu: %s txrxFlags %#02x --> %02x\n", os_getTime(), func, LMIC.txrxFlags, mask); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": %s txrxFlags %#02x --> %02x\n", os_getTime(), func, LMIC.txrxFlags, mask);
#endif #endif
LMIC.txrxFlags = mask; LMIC.txrxFlags = mask;
} }
@ -272,29 +274,6 @@ ostime_t calcAirTime (rps_t rps, u1_t plen) {
return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div; return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div;
} }
extern inline rps_t updr2rps (dr_t dr);
extern inline rps_t dndr2rps (dr_t dr);
extern inline int isFasterDR (dr_t dr1, dr_t dr2);
extern inline int isSlowerDR (dr_t dr1, dr_t dr2);
extern inline dr_t incDR (dr_t dr);
extern inline dr_t decDR (dr_t dr);
extern inline dr_t assertDR (dr_t dr);
extern inline dr_t validDR (dr_t dr);
extern inline dr_t lowerDR (dr_t dr, u1_t n);
extern inline sf_t getSf (rps_t params);
extern inline rps_t setSf (rps_t params, sf_t sf);
extern inline bw_t getBw (rps_t params);
extern inline rps_t setBw (rps_t params, bw_t cr);
extern inline cr_t getCr (rps_t params);
extern inline rps_t setCr (rps_t params, cr_t cr);
extern inline int getNocrc (rps_t params);
extern inline rps_t setNocrc (rps_t params, int nocrc);
extern inline int getIh (rps_t params);
extern inline rps_t setIh (rps_t params, int ih);
extern inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc);
extern inline int sameSfBw (rps_t r1, rps_t r2);
// END LORA // END LORA
// ================================================================================ // ================================================================================
@ -414,6 +393,8 @@ static void txDelay (ostime_t reftime, u1_t secSpan) {
void LMICcore_setDrJoin (u1_t reason, u1_t dr) { void LMICcore_setDrJoin (u1_t reason, u1_t dr) {
LMIC_EV_PARAMETER(reason);
EV(drChange, INFO, (e_.reason = reason, EV(drChange, INFO, (e_.reason = reason,
e_.deveui = MAIN::CDEV->getEui(), e_.deveui = MAIN::CDEV->getEui(),
e_.dr = dr|DR_PAGE, e_.dr = dr|DR_PAGE,
@ -426,6 +407,8 @@ void LMICcore_setDrJoin (u1_t reason, u1_t dr) {
static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) {
LMIC_EV_PARAMETER(reason);
EV(drChange, INFO, (e_.reason = reason, EV(drChange, INFO, (e_.reason = reason,
e_.deveui = MAIN::CDEV->getEui(), e_.deveui = MAIN::CDEV->getEui(),
e_.dr = dr|DR_PAGE, e_.dr = dr|DR_PAGE,
@ -462,6 +445,8 @@ void LMIC_setPingable (u1_t intvExp) {
#endif // !DISABLE_PING #endif // !DISABLE_PING
static void runEngineUpdate (xref2osjob_t osjob) { static void runEngineUpdate (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
engineUpdate(); engineUpdate();
} }
@ -476,6 +461,8 @@ static void reportEvent (ev_t ev) {
static void runReset (xref2osjob_t osjob) { static void runReset (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
// Disable session // Disable session
LMIC_reset(); LMIC_reset();
#if !defined(DISABLE_JOIN) #if !defined(DISABLE_JOIN)
@ -594,7 +581,7 @@ scan_mac_cmds(
// of contiguous commands (whatever that means), and ignore the // of contiguous commands (whatever that means), and ignore the
// data rate, NbTrans (uprpt) and txPow until the last one. // data rate, NbTrans (uprpt) and txPow until the last one.
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
LMIC_DEBUG_PRINTF("%lu: LinkAdrReq: p1:%02x chmap:%04x chpage:%02x uprt:%02x ans:%02x\n", LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": LinkAdrReq: p1:%02x chmap:%04x chpage:%02x uprt:%02x ans:%02x\n",
os_getTime(), p1, chmap, chpage, uprpt, LMIC.ladrAns os_getTime(), p1, chmap, chpage, uprpt, LMIC.ladrAns
); );
#endif /* LMIC_DEBUG_LEVEL */ #endif /* LMIC_DEBUG_LEVEL */
@ -726,6 +713,38 @@ scan_mac_cmds(
oidx += 2; oidx += 2;
continue; continue;
} /* end case */ } /* end case */
case MCMD_DeviceTimeAns: {
#if LMIC_ENABLE_DeviceTimeReq
// don't process a spurious downlink.
if ( LMIC.txDeviceTimeReqState == lmic_RequestTimeState_rx ) {
// remember that it's time to notify the client.
LMIC.txDeviceTimeReqState = lmic_RequestTimeState_success;
// the network time is linked to the time of the last TX.
LMIC.localDeviceTime = LMIC.txend;
// save the network time.
// The first 4 bytes contain the seconds since the GPS epoch
// (i.e January the 6th 1980 at 00:00:00 UTC).
// Note: as per the LoRaWAN specs, the octet order for all
// multi-octet fields is little endian
// Note: the casts are necessary, because opts is an array of
// single byte values, and they might overflow when shifted
LMIC.netDeviceTime = ( (lmic_gpstime_t) opts[oidx + 1] ) |
(((lmic_gpstime_t) opts[oidx + 2]) << 8) |
(((lmic_gpstime_t) opts[oidx + 3]) << 16) |
(((lmic_gpstime_t) opts[oidx + 4]) << 24);
// The 5th byte contains the fractional seconds in 2^-8 second steps
LMIC.netDeviceTimeFrac = opts[oidx + 5];
#if LMIC_DEBUG_LEVEL > 0
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": MAC command DeviceTimeAns received: seconds_since_gps_epoch=%"PRIu32", fractional_seconds=%d\n", os_getTime(), LMIC.netDeviceTime, LMIC.netDeviceTimeFrac);
#endif
}
#endif // LMIC_ENABLE_DeviceTimeReq
oidx += 6;
continue;
} /* end case */
} /* end switch */ } /* end switch */
/* unrecognized mac commands fall out of switch to here */ /* unrecognized mac commands fall out of switch to here */
EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
@ -757,7 +776,7 @@ static bit_t decodeFrame (void) {
e_.info2 = hdr + (dlen<<8))); e_.info2 = hdr + (dlen<<8)));
norx: norx:
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
LMIC_DEBUG_PRINTF("%lu: Invalid downlink, window=%s\n", os_getTime(), window); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Invalid downlink, window=%s\n", os_getTime(), window);
#endif #endif
LMIC.dataLen = 0; LMIC.dataLen = 0;
return 0; return 0;
@ -849,7 +868,7 @@ static bit_t decodeFrame (void) {
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
// Process OPTS // Process OPTS
LMIC_DEBUG_PRINTF("%lu: process options (olen=%#x)\n", os_getTime(), olen); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": process options (olen=%#x)\n", os_getTime(), olen);
#endif #endif
xref2u1_t opts = &d[OFF_DAT_OPTS]; xref2u1_t opts = &d[OFF_DAT_OPTS];
@ -868,13 +887,13 @@ static bit_t decodeFrame (void) {
if (port == 0) { if (port == 0) {
// this is a mac command. scan the options. // this is a mac command. scan the options.
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
LMIC_DEBUG_PRINTF("%lu: process mac commands for port 0 (olen=%#x)\n", os_getTime(), pend-poff); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": process mac commands for port 0 (olen=%#x)\n", os_getTime(), pend-poff);
#endif #endif
int optendindex = scan_mac_cmds(d+poff, pend-poff); int optendindex = scan_mac_cmds(d+poff, pend-poff);
if (optendindex != pend-poff) { if (optendindex != pend-poff) {
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
LMIC_DEBUG_PRINTF( LMIC_DEBUG_PRINTF(
"%lu: error processing mac commands for port 0 " "%"LMIC_PRId_ostime_t": error processing mac commands for port 0 "
"(len=%#x, optendindex=%#x)\n", "(len=%#x, optendindex=%#x)\n",
os_getTime(), pend-poff, optendindex os_getTime(), pend-poff, optendindex
); );
@ -911,7 +930,7 @@ static bit_t decodeFrame (void) {
e_.info = seqno, e_.info = seqno,
e_.info2 = ackup)); e_.info2 = ackup));
#if LMIC_DEBUG_LEVEL > 1 #if LMIC_DEBUG_LEVEL > 1
LMIC_DEBUG_PRINTF("%lu: ??ack error ack=%d txCnt=%d\n", os_getTime(), ackup, LMIC.txCnt); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": ??ack error ack=%d txCnt=%d\n", os_getTime(), ackup, LMIC.txCnt);
#endif #endif
} }
@ -928,7 +947,7 @@ static bit_t decodeFrame (void) {
LMIC.dataLen = pend-poff; LMIC.dataLen = pend-poff;
} }
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
LMIC_DEBUG_PRINTF("%lu: Received downlink, window=%s, port=%d, ack=%d, txrxFlags=%#x\n", os_getTime(), window, port, ackup, LMIC.txrxFlags); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Received downlink, window=%s, port=%d, ack=%d, txrxFlags=%#x\n", os_getTime(), window, port, ackup, LMIC.txrxFlags);
#endif #endif
return 1; return 1;
} }
@ -977,7 +996,7 @@ static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) {
// (again note that hsym is half a sumbol time, so no /2 needed) // (again note that hsym is half a sumbol time, so no /2 needed)
LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym; LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym;
LMIC_X_DEBUG_PRINTF("%lu: sched Rx12 %lu\n", os_getTime(), LMIC.rxtime - RX_RAMPUP); LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.rxtime - RX_RAMPUP);
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
} }
@ -1020,6 +1039,8 @@ static void txDone (ostime_t delay, osjobcb_t func) {
#if !defined(DISABLE_JOIN) #if !defined(DISABLE_JOIN)
static void onJoinFailed (xref2osjob_t osjob) { static void onJoinFailed (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
// Notify app - must call LMIC_reset() to stop joining // Notify app - must call LMIC_reset() to stop joining
// otherwise join procedure continues. // otherwise join procedure continues.
reportEvent(EV_JOIN_FAILED); reportEvent(EV_JOIN_FAILED);
@ -1073,6 +1094,8 @@ static bit_t processJoinAccept (void) {
u1_t hdr = LMIC.frame[0]; u1_t hdr = LMIC.frame[0];
u1_t dlen = LMIC.dataLen; u1_t dlen = LMIC.dataLen;
u4_t mic = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt! u4_t mic = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt!
LMIC_EV_VARIABLE(mic); // only used by EV().
if( (dlen != LEN_JA && dlen != LEN_JAEXT) if( (dlen != LEN_JA && dlen != LEN_JAEXT)
|| (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) { || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) {
EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME, EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
@ -1109,7 +1132,7 @@ static bit_t processJoinAccept (void) {
if( freq ) { if( freq ) {
LMIC_setupChannel(chidx, freq, 0, -1); LMIC_setupChannel(chidx, freq, 0, -1);
#if LMIC_DEBUG_LEVEL > 1 #if LMIC_DEBUG_LEVEL > 1
LMIC_DEBUG_PRINTF("%lu: Setup channel, idx=%d, freq=%lu\n", os_getTime(), chidx, (unsigned long)freq); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq);
#endif #endif
} }
} }
@ -1161,6 +1184,8 @@ static bit_t processJoinAccept (void) {
static void processRx2Jacc (xref2osjob_t osjob) { static void processRx2Jacc (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
if( LMIC.dataLen == 0 ) { if( LMIC.dataLen == 0 ) {
initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot
} }
@ -1169,23 +1194,31 @@ static void processRx2Jacc (xref2osjob_t osjob) {
static void setupRx2Jacc (xref2osjob_t osjob) { static void setupRx2Jacc (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
LMIC.osjob.func = FUNC_ADDR(processRx2Jacc); LMIC.osjob.func = FUNC_ADDR(processRx2Jacc);
setupRx2(); setupRx2();
} }
static void processRx1Jacc (xref2osjob_t osjob) { static void processRx1Jacc (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
if( LMIC.dataLen == 0 || !processJoinAccept() ) if( LMIC.dataLen == 0 || !processJoinAccept() )
schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr); schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr);
} }
static void setupRx1Jacc (xref2osjob_t osjob) { static void setupRx1Jacc (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
setupRx1(FUNC_ADDR(processRx1Jacc)); setupRx1(FUNC_ADDR(processRx1Jacc));
} }
static void jreqDone (xref2osjob_t osjob) { static void jreqDone (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc)); txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc));
} }
@ -1197,10 +1230,14 @@ static void jreqDone (xref2osjob_t osjob) {
static bit_t processDnData(void); static bit_t processDnData(void);
static void processRx2DnDataDelay (xref2osjob_t osjob) { static void processRx2DnDataDelay (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
processDnData(); processDnData();
} }
static void processRx2DnData (xref2osjob_t osjob) { static void processRx2DnData (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
if( LMIC.dataLen == 0 ) { if( LMIC.dataLen == 0 ) {
initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot
// Delay callback processing to avoid up TX while gateway is txing our missed frame! // Delay callback processing to avoid up TX while gateway is txing our missed frame!
@ -1215,23 +1252,31 @@ static void processRx2DnData (xref2osjob_t osjob) {
static void setupRx2DnData (xref2osjob_t osjob) { static void setupRx2DnData (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
LMIC.osjob.func = FUNC_ADDR(processRx2DnData); LMIC.osjob.func = FUNC_ADDR(processRx2DnData);
setupRx2(); setupRx2();
} }
static void processRx1DnData (xref2osjob_t osjob) { static void processRx1DnData (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
if( LMIC.dataLen == 0 || !processDnData() ) if( LMIC.dataLen == 0 || !processDnData() )
schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr); schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr);
} }
static void setupRx1DnData (xref2osjob_t osjob) { static void setupRx1DnData (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
setupRx1(FUNC_ADDR(processRx1DnData)); setupRx1(FUNC_ADDR(processRx1DnData));
} }
static void updataDone (xref2osjob_t osjob) { static void updataDone (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData)); txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData));
} }
@ -1315,6 +1360,13 @@ static void buildDataFrame (void) {
LMIC.txParamSetupAns = 0; LMIC.txParamSetupAns = 0;
} }
#endif #endif
#if LMIC_ENABLE_DeviceTimeReq
if ( LMIC.txDeviceTimeReqState == lmic_RequestTimeState_tx ) {
LMIC.frame[end+0] = MCMD_DeviceTimeReq;
end += 1;
LMIC.txDeviceTimeReqState = lmic_RequestTimeState_rx;
}
#endif // LMIC_ENABLE_DeviceTimeReq
ASSERT(end <= OFF_DAT_OPTS+16); ASSERT(end <= OFF_DAT_OPTS+16);
u1_t flen = end + (txdata ? 5+dlen : 4); u1_t flen = end + (txdata ? 5+dlen : 4);
@ -1376,7 +1428,9 @@ static void buildDataFrame (void) {
#if !defined(DISABLE_BEACONS) #if !defined(DISABLE_BEACONS)
// Callback from HAL during scan mode or when job timer expires. // Callback from HAL during scan mode or when job timer expires.
static void onBcnRx (xref2osjob_t job) { static void onBcnRx (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
// If we arrive via job timer make sure to put radio to rest. // If we arrive via job timer make sure to put radio to rest.
os_radio(RADIO_RST); os_radio(RADIO_RST);
os_clearCallback(&LMIC.osjob); os_clearCallback(&LMIC.osjob);
@ -1497,6 +1551,8 @@ static void buildJoinRequest (u1_t ftype) {
} }
static void startJoining (xref2osjob_t osjob) { static void startJoining (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
reportEvent(EV_JOINING); reportEvent(EV_JOINING);
} }
@ -1530,6 +1586,8 @@ bit_t LMIC_startJoining (void) {
#if !defined(DISABLE_PING) #if !defined(DISABLE_PING)
static void processPingRx (xref2osjob_t osjob) { static void processPingRx (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
if( LMIC.dataLen != 0 ) { if( LMIC.dataLen != 0 ) {
initTxrxFlags(__func__, TXRX_PING); initTxrxFlags(__func__, TXRX_PING);
if( decodeFrame() ) { if( decodeFrame() ) {
@ -1568,6 +1626,24 @@ static bit_t processDnData (void) {
LMIC.dataBeg = LMIC.dataLen = 0; LMIC.dataBeg = LMIC.dataLen = 0;
txcomplete: txcomplete:
LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND);
#if LMIC_ENABLE_DeviceTimeReq
lmic_request_time_state_t const requestTimeState = LMIC.txDeviceTimeReqState;
if ( requestTimeState != lmic_RequestTimeState_idle ) {
lmic_request_network_time_cb_t * const pNetworkTimeCb = LMIC.pNetworkTimeCb;
int flagSuccess = (LMIC.txDeviceTimeReqState == lmic_RequestTimeState_success);
LMIC.txDeviceTimeReqState = lmic_RequestTimeState_idle;
if (pNetworkTimeCb != NULL) {
// reset the callback, so that the user's routine
// can post another request if desired.
LMIC.pNetworkTimeCb = NULL;
// call the user's notification routine.
(*pNetworkTimeCb)(LMIC.pNetworkTimeUserData, flagSuccess);
}
}
#endif // LMIC_ENABLE_DeviceTimeReq
if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) {
LMIC.opmode &= ~OP_LINKDEAD; LMIC.opmode &= ~OP_LINKDEAD;
reportEvent(EV_LINK_ALIVE); reportEvent(EV_LINK_ALIVE);
@ -1611,6 +1687,8 @@ static bit_t processDnData (void) {
#if !defined(DISABLE_BEACONS) #if !defined(DISABLE_BEACONS)
static void processBeacon (xref2osjob_t osjob) { static void processBeacon (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite
u1_t flags = LMIC.bcninfo.flags; u1_t flags = LMIC.bcninfo.flags;
ev_t ev; ev_t ev;
@ -1672,6 +1750,8 @@ static void processBeacon (xref2osjob_t osjob) {
static void startRxBcn (xref2osjob_t osjob) { static void startRxBcn (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
LMIC.osjob.func = FUNC_ADDR(processBeacon); LMIC.osjob.func = FUNC_ADDR(processBeacon);
os_radio(RADIO_RX); os_radio(RADIO_RX);
} }
@ -1680,6 +1760,8 @@ static void startRxBcn (xref2osjob_t osjob) {
#if !defined(DISABLE_PING) #if !defined(DISABLE_PING)
static void startRxPing (xref2osjob_t osjob) { static void startRxPing (xref2osjob_t osjob) {
LMIC_API_PARAMETER(osjob);
LMIC.osjob.func = FUNC_ADDR(processPingRx); LMIC.osjob.func = FUNC_ADDR(processPingRx);
os_radio(RADIO_RX); os_radio(RADIO_RX);
} }
@ -1689,7 +1771,7 @@ static void startRxPing (xref2osjob_t osjob) {
// Decide what to do next for the MAC layer of a device // Decide what to do next for the MAC layer of a device
static void engineUpdate (void) { static void engineUpdate (void) {
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
LMIC_DEBUG_PRINTF("%lu: engineUpdate, opmode=0x%x\n", os_getTime(), LMIC.opmode); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": engineUpdate, opmode=0x%x\n", os_getTime(), LMIC.opmode);
#endif #endif
// Check for ongoing state: scan or TX/RX transaction // Check for ongoing state: scan or TX/RX transaction
if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 )
@ -1703,10 +1785,11 @@ static void engineUpdate (void) {
#endif // !DISABLE_JOIN #endif // !DISABLE_JOIN
ostime_t now = os_getTime(); ostime_t now = os_getTime();
ostime_t rxtime = 0;
ostime_t txbeg = 0; ostime_t txbeg = 0;
#if !defined(DISABLE_BEACONS) #if !defined(DISABLE_BEACONS)
ostime_t rxtime = 0;
if( (LMIC.opmode & OP_TRACK) != 0 ) { if( (LMIC.opmode & OP_TRACK) != 0 ) {
// We are tracking a beacon // We are tracking a beacon
ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 ); ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 );
@ -1848,7 +1931,7 @@ static void engineUpdate (void) {
e_.eui = MAIN::CDEV->getEui(), e_.eui = MAIN::CDEV->getEui(),
e_.info = osticks2ms(txbeg-now), e_.info = osticks2ms(txbeg-now),
e_.info2 = LMIC.seqnoUp-1)); e_.info2 = LMIC.seqnoUp-1));
LMIC_X_DEBUG_PRINTF("%lu: next engine update in %lu\n", now, txbeg-TX_RAMPUP); LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": next engine update in %"LMIC_PRId_ostime_t"\n", now, txbeg-TX_RAMPUP);
os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate));
} }
@ -1902,6 +1985,11 @@ void LMIC_reset (void) {
DO_DEVDB(LMIC.ping.dr, pingDr); DO_DEVDB(LMIC.ping.dr, pingDr);
DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); DO_DEVDB(LMIC.ping.intvExp, pingIntvExp);
#endif // !DISABLE_PING #endif // !DISABLE_PING
#if LMIC_ENABLE_DeviceTimeReq
LMIC.txDeviceTimeReqState = lmic_RequestTimeState_idle;
LMIC.netDeviceTime = 0; // the "invalid" time.
LMIC.netDeviceTimeFrac = 0;
#endif // LMIC_ENABLE_DeviceTimeReq
} }
@ -1943,7 +2031,6 @@ int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) {
return 0; return 0;
} }
// Send a payload-less message to signal device is alive // Send a payload-less message to signal device is alive
void LMIC_sendAlive (void) { void LMIC_sendAlive (void) {
LMIC.opmode |= OP_POLL; LMIC.opmode |= OP_POLL;
@ -2041,3 +2128,36 @@ void LMIC_getSessionKeys (u4_t *netid, devaddr_t *devaddr, xref2u1_t nwkKey, xre
memcpy(artKey, LMIC.artKey, sizeof(LMIC.artKey)); memcpy(artKey, LMIC.artKey, sizeof(LMIC.artKey));
memcpy(nwkKey, LMIC.nwkKey, sizeof(LMIC.nwkKey)); memcpy(nwkKey, LMIC.nwkKey, sizeof(LMIC.nwkKey));
} }
// \brief post an asynchronous request for the network time.
void LMIC_requestNetworkTime(lmic_request_network_time_cb_t *pCallbackfn, void *pUserData) {
#if LMIC_ENABLE_DeviceTimeReq
if (LMIC.txDeviceTimeReqState == lmic_RequestTimeState_idle) {
LMIC.txDeviceTimeReqState = lmic_RequestTimeState_tx;
LMIC.pNetworkTimeCb = pCallbackfn;
LMIC.pNetworkTimeUserData = pUserData;
return;
}
#endif // LMIC_ENABLE_DeviceTimeReq
// if no device time support, or if not in proper state,
// report a failure.
if (pCallbackfn != NULL)
(*pCallbackfn)(pUserData, /* false */ 0);
}
// \brief return local/remote time pair (if valid, and DeviceTimeReq enabled),
// return true for success, false for error. We adjust the sampled OS time
// back in time to the nearest second boundary.
int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference) {
#if LMIC_ENABLE_DeviceTimeReq
if (pReference != NULL && // valid parameter, and
LMIC.netDeviceTime != 0) { // ... we have a reasonable answer.
const ostime_t tAdjust = LMIC.netDeviceTimeFrac * ms2osticks(1000) / 256;
pReference->tLocal = LMIC.localDeviceTime - tAdjust;
pReference->tNetwork = LMIC.netDeviceTime;
return 1;
}
#endif // LMIC_ENABLE_DeviceTimeReq
return 0;
}

View File

@ -105,7 +105,7 @@ extern "C"{
#define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \ #define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \
(((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local)) (((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local))
#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(2, 2, 2, 0) #define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(2, 2, 2, 0) /* v2.2.2 */
#define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \ #define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \
(((v) >> 24u) & 0xFFu) (((v) >> 24u) & 0xFFu)
@ -243,6 +243,35 @@ enum {
MAX_CLOCK_ERROR = 65536, MAX_CLOCK_ERROR = 65536,
}; };
// network time request callback function
// defined unconditionally, because APIs and types can't change based on config.
// This is called when a time-request succeeds or when we get a downlink
// without time request, "completing" the pending time request.
typedef void lmic_request_network_time_cb_t(void *pUserData, int flagSuccess);
// how the network represents time.
typedef u4_t lmic_gpstime_t;
// rather than deal with 1/256 second tick, we adjust ostime back
// (as it's high res) to match tNetwork.
typedef struct lmic_time_reference_s lmic_time_reference_t;
struct lmic_time_reference_s {
// our best idea of when we sent the uplink (end of packet).
ostime_t tLocal;
// the network's best idea of when we sent the uplink.
lmic_gpstime_t tNetwork;
};
enum lmic_request_time_state_e {
lmic_RequestTimeState_idle = 0, // we're not doing anything
lmic_RequestTimeState_tx, // we want to tx a time request on next uplink
lmic_RequestTimeState_rx, // we have tx'ed, next downlink completes.
lmic_RequestTimeState_success // we sucessfully got time.
};
typedef u1_t lmic_request_time_state_t;
struct lmic_t { struct lmic_t {
// Radio settings TX/RX (also accessed by HAL) // Radio settings TX/RX (also accessed by HAL)
ostime_t txend; ostime_t txend;
@ -306,6 +335,14 @@ struct lmic_t {
devaddr_t devaddr; devaddr_t devaddr;
u4_t seqnoDn; // device level down stream seqno u4_t seqnoDn; // device level down stream seqno
u4_t seqnoUp; u4_t seqnoUp;
#if LMIC_ENABLE_DeviceTimeReq
// put here for alignment, to reduce RAM use.
ostime_t localDeviceTime; // the LMIC.txend value for last DeviceTimeAns
lmic_gpstime_t netDeviceTime; // the netDeviceTime for lastDeviceTimeAns
// zero ==> not valid.
lmic_request_network_time_cb_t *pNetworkTimeCb; // call-back routine
void *pNetworkTimeUserData; // call-back data
#endif // LMIC_ENABLE_DeviceTimeReq
u1_t dnConf; // dn frame confirm pending: LORA::FCT_ACK or 0 u1_t dnConf; // dn frame confirm pending: LORA::FCT_ACK or 0
s1_t adrAckReq; // counter until we reset data rate (0=off) s1_t adrAckReq; // counter until we reset data rate (0=off)
@ -329,6 +366,10 @@ struct lmic_t {
bit_t txParamSetupAns; // transmit setup answer pending. bit_t txParamSetupAns; // transmit setup answer pending.
u1_t txParam; // the saved TX param byte. u1_t txParam; // the saved TX param byte.
#endif #endif
#if LMIC_ENABLE_DeviceTimeReq
lmic_request_time_state_t txDeviceTimeReqState; // current state, initially idle.
u1_t netDeviceTimeFrac; // updated on any DeviceTimeAns.
#endif
// rx1DrOffset is the offset from uplink to downlink datarate // rx1DrOffset is the offset from uplink to downlink datarate
u1_t rx1DrOffset; // captured from join. zero by default. u1_t rx1DrOffset; // captured from join. zero by default.
@ -368,6 +409,7 @@ struct lmic_t {
u1_t noRXIQinversion; u1_t noRXIQinversion;
}; };
//! \var struct lmic_t LMIC //! \var struct lmic_t LMIC
//! The state of LMIC MAC layer is encapsulated in this variable. //! The state of LMIC MAC layer is encapsulated in this variable.
DECLARE_LMIC; //!< \internal DECLARE_LMIC; //!< \internal
@ -417,6 +459,9 @@ u4_t LMIC_getSeqnoUp (void);
u4_t LMIC_setSeqnoUp (u4_t); u4_t LMIC_setSeqnoUp (u4_t);
void LMIC_getSessionKeys (u4_t *netid, devaddr_t *devaddr, xref2u1_t nwkKey, xref2u1_t artKey); void LMIC_getSessionKeys (u4_t *netid, devaddr_t *devaddr, xref2u1_t nwkKey, xref2u1_t artKey);
void LMIC_requestNetworkTime(lmic_request_network_time_cb_t *pCallbackfn, void *pUserData);
int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference);
// Declare onEvent() function, to make sure any definition will have the // Declare onEvent() function, to make sure any definition will have the
// C conventions, even when in a C++ file. // C conventions, even when in a C++ file.
DECL_ON_LMIC_EVENT; DECL_ON_LMIC_EVENT;

View File

@ -76,11 +76,15 @@ static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
static uint8_t static uint8_t
LMICas923_getUplinkDwellBit(uint8_t mcmd_txparam) { LMICas923_getUplinkDwellBit(uint8_t mcmd_txparam) {
LMIC_API_PARAMETER(mcmd_txparam);
return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0; return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0;
} }
static uint8_t static uint8_t
LMICas923_getDownlinkDwellBit(uint8_t mcmd_txparam) { LMICas923_getDownlinkDwellBit(uint8_t mcmd_txparam) {
LMIC_API_PARAMETER(mcmd_txparam);
return (LMIC.txParam & MCMD_TxParam_RxDWELL_MASK) != 0; return (LMIC.txParam & MCMD_TxParam_RxDWELL_MASK) != 0;
} }
@ -164,6 +168,8 @@ static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = {
// as923 ignores join, becuase the channel setup is the same either way. // as923 ignores join, becuase the channel setup is the same either way.
void LMICas923_initDefaultChannels(bit_t join) { void LMICas923_initDefaultChannels(bit_t join) {
LMIC_API_PARAMETER(join);
os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq));
os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); os_clearMem(&LMIC.bands, sizeof(LMIC.bands));

View File

@ -101,6 +101,11 @@ u4_t LMICau921_convFreq(xref2cu1_t ptr) {
// au921: no support for xchannels. // au921: no support for xchannels.
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
LMIC_API_PARAMETER(chidx);
LMIC_API_PARAMETER(freq);
LMIC_API_PARAMETER(drmap);
LMIC_API_PARAMETER(band);
return 0; // all channels are hardwired. return 0; // all channels are hardwired.
} }

View File

@ -113,6 +113,12 @@ Revision history:
// following values. These are in order of the sections in the manual. Not all of the // following values. These are in order of the sections in the manual. Not all of the
// below are supported yet. // below are supported yet.
// //
// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in
// the below.
//
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
//
# define CFG_LMIC_REGION_MASK \ # define CFG_LMIC_REGION_MASK \
((defined(CFG_eu868) << LMIC_REGION_eu868) | \ ((defined(CFG_eu868) << LMIC_REGION_eu868) | \
(defined(CFG_us915) << LMIC_REGION_us915) | \ (defined(CFG_us915) << LMIC_REGION_us915) | \
@ -126,6 +132,8 @@ Revision history:
0) 0)
// the selected region. // the selected region.
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
#if defined(CFG_eu868) #if defined(CFG_eu868)
# define CFG_region LMIC_REGION_eu868 # define CFG_region LMIC_REGION_eu868
#elif defined(CFG_us915) #elif defined(CFG_us915)
@ -138,6 +146,10 @@ Revision history:
# define CFG_region LMIC_REGION_au921 # define CFG_region LMIC_REGION_au921
#elif defined(CFG_cn490) #elif defined(CFG_cn490)
# define CFG_region LMIC_REGION_cn490 # define CFG_region LMIC_REGION_cn490
#elif defined(CFG_as923jp)
# define CFG_as923 1 /* CFG_as923jp implies CFG_as923 */
# define CFG_region LMIC_REGION_as923
# define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP
#elif defined(CFG_as923) #elif defined(CFG_as923)
# define CFG_region LMIC_REGION_as923 # define CFG_region LMIC_REGION_as923
#elif defined(CFG_kr921) #elif defined(CFG_kr921)
@ -148,7 +160,11 @@ Revision history:
# define CFG_region 0 # define CFG_region 0
#endif #endif
// finally the mask of` US-like and EU-like regions // a bitmask of EU-like regions -- these are regions which have up to 16
// channels indidually programmable via downloink.
//
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
#define CFG_LMIC_EU_like_MASK ( \ #define CFG_LMIC_EU_like_MASK ( \
(1 << LMIC_REGION_eu868) | \ (1 << LMIC_REGION_eu868) | \
/* (1 << LMIC_REGION_us915) | */ \ /* (1 << LMIC_REGION_us915) | */ \
@ -161,6 +177,12 @@ Revision history:
(1 << LMIC_REGION_in866) | \ (1 << LMIC_REGION_in866) | \
0) 0)
// a bitmask of` US-like regions -- these are regions with 64 fixed 125 kHz channels
// overlaid by 8 500 kHz channels. The channel frequencies can't be changed, but
// subsets of channels can be selected via masks.
//
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
#define CFG_LMIC_US_like_MASK ( \ #define CFG_LMIC_US_like_MASK ( \
/* (1 << LMIC_REGION_eu868) | */ \ /* (1 << LMIC_REGION_eu868) | */ \
(1 << LMIC_REGION_us915) | \ (1 << LMIC_REGION_us915) | \
@ -173,9 +195,12 @@ Revision history:
/* (1 << LMIC_REGION_in866) | */ \ /* (1 << LMIC_REGION_in866) | */ \
0) 0)
//
// booleans that are true if the configured region is EU-like or US-like.
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
//
#define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK)) #define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK))
#define CFG_LMIC_US_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_US_like_MASK)) #define CFG_LMIC_US_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_US_like_MASK))
#endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */ #endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */

View File

@ -33,9 +33,11 @@
#if CFG_LMIC_EU_like #if CFG_LMIC_EU_like
void LMIC_enableSubBand(u1_t band) { void LMIC_enableSubBand(u1_t band) {
LMIC_API_PARAMETER(band);
} }
void LMIC_disableSubBand(u1_t band) { void LMIC_disableSubBand(u1_t band) {
LMIC_API_PARAMETER(band);
} }
void LMIC_disableChannel(u1_t channel) { void LMIC_disableChannel(u1_t channel) {
@ -46,6 +48,7 @@ void LMIC_disableChannel(u1_t channel) {
// this is a no-op provided for compatibilty // this is a no-op provided for compatibilty
void LMIC_enableChannel(u1_t channel) { void LMIC_enableChannel(u1_t channel) {
LMIC_API_PARAMETER(channel);
} }
u1_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap) { u1_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap) {

View File

@ -95,6 +95,8 @@ static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = {
// india ignores join, becuase the channel setup is the same either way. // india ignores join, becuase the channel setup is the same either way.
void LMICin866_initDefaultChannels(bit_t join) { void LMICin866_initDefaultChannels(bit_t join) {
LMIC_API_PARAMETER(join);
os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq));
os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); os_clearMem(&LMIC.bands, sizeof(LMIC.bands));

View File

@ -87,6 +87,8 @@ u4_t LMICus915_convFreq(xref2cu1_t ptr) {
} }
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
LMIC_API_PARAMETER(band);
if (chidx < 72 || chidx >= 72 + MAX_XCHANNELS) if (chidx < 72 || chidx >= 72 + MAX_XCHANNELS)
return 0; // channels 0..71 are hardwired return 0; // channels 0..71 are hardwired
LMIC.xchFreq[chidx - 72] = freq; LMIC.xchFreq[chidx - 72] = freq;

View File

@ -71,12 +71,18 @@ static void setNextChannel(uint start, uint end, uint count) {
bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
LMIC_API_PARAMETER(bandidx);
LMIC_API_PARAMETER(txpow);
LMIC_API_PARAMETER(txcap);
// nothing; just succeed. // nothing; just succeed.
return 1; return 1;
} }
void LMICuslike_initDefaultChannels(bit_t fJoin) { void LMICuslike_initDefaultChannels(bit_t fJoin) {
LMIC_API_PARAMETER(fJoin);
// things work the same for join as normal. // things work the same for join as normal.
for (u1_t i = 0; i<4; i++) for (u1_t i = 0; i<4; i++)
LMIC.channelMap[i] = 0xFFFF; LMIC.channelMap[i] = 0xFFFF;

View File

@ -449,6 +449,7 @@ enum {
MCMD_RXTimingSetupAns = 0x08, // : - MCMD_RXTimingSetupAns = 0x08, // : -
MCMD_TxParamSetupAns = 0x09, // : - MCMD_TxParamSetupAns = 0x09, // : -
MCMD_DIChannelAns = 0x0A, // : u1: [7-2]:RFU 1:exists 0:OK MCMD_DIChannelAns = 0x0A, // : u1: [7-2]:RFU 1:exists 0:OK
MCMD_DeviceTimeReq = 0x0D,
// Class B // Class B
MCMD_PING_IND = 0x10, // - pingability indic : u1: 7=RFU, 6-4:interval, 3-0:datarate MCMD_PING_IND = 0x10, // - pingability indic : u1: 7=RFU, 6-4:interval, 3-0:datarate
@ -468,6 +469,7 @@ enum {
MCMD_RXTimingSetupReq = 0x08, // : u1: [7-4]:RFU [3-0]: Delay 1-15s (0 => 1) MCMD_RXTimingSetupReq = 0x08, // : u1: [7-4]:RFU [3-0]: Delay 1-15s (0 => 1)
MCMD_TxParamSetupReq = 0x09, // : u1: [7-6]:RFU [5:4]: dl dwell/ul dwell [3:0] max EIRP MCMD_TxParamSetupReq = 0x09, // : u1: [7-6]:RFU [5:4]: dl dwell/ul dwell [3:0] max EIRP
MCMD_DIChannelReq = 0x0A, // : u1: channel, u3: frequency MCMD_DIChannelReq = 0x0A, // : u1: channel, u3: frequency
MCMD_DeviceTimeAns = 0x0D,
// Class B // Class B
MCMD_PING_SET = 0x11, // set ping freq : u3: freq MCMD_PING_SET = 0x11, // set ping freq : u3: freq
@ -583,33 +585,33 @@ typedef u4_t devaddr_t;
// RX quality (device) // RX quality (device)
enum { RSSI_OFF=64, SNR_SCALEUP=4 }; enum { RSSI_OFF=64, SNR_SCALEUP=4 };
inline sf_t getSf (rps_t params) { return (sf_t)(params & 0x7); } static inline sf_t getSf (rps_t params) { return (sf_t)(params & 0x7); }
inline rps_t setSf (rps_t params, sf_t sf) { return (rps_t)((params & ~0x7) | sf); } static inline rps_t setSf (rps_t params, sf_t sf) { return (rps_t)((params & ~0x7) | sf); }
inline bw_t getBw (rps_t params) { return (bw_t)((params >> 3) & 0x3); } static inline bw_t getBw (rps_t params) { return (bw_t)((params >> 3) & 0x3); }
inline rps_t setBw (rps_t params, bw_t cr) { return (rps_t)((params & ~0x18) | (cr<<3)); } static inline rps_t setBw (rps_t params, bw_t cr) { return (rps_t)((params & ~0x18) | (cr<<3)); }
inline cr_t getCr (rps_t params) { return (cr_t)((params >> 5) & 0x3); } static inline cr_t getCr (rps_t params) { return (cr_t)((params >> 5) & 0x3); }
inline rps_t setCr (rps_t params, cr_t cr) { return (rps_t)((params & ~0x60) | (cr<<5)); } static inline rps_t setCr (rps_t params, cr_t cr) { return (rps_t)((params & ~0x60) | (cr<<5)); }
inline int getNocrc(rps_t params) { return ((params >> 7) & 0x1); } static inline int getNocrc(rps_t params) { return ((params >> 7) & 0x1); }
inline rps_t setNocrc(rps_t params, int nocrc) { return (rps_t)((params & ~0x80) | (nocrc<<7)); } static inline rps_t setNocrc(rps_t params, int nocrc) { return (rps_t)((params & ~0x80) | (nocrc<<7)); }
inline int getIh (rps_t params) { return ((params >> 8) & 0xFF); } static inline int getIh (rps_t params) { return ((params >> 8) & 0xFF); }
inline rps_t setIh (rps_t params, int ih) { return (rps_t)((params & ~0xFF00) | (ih<<8)); } static inline rps_t setIh (rps_t params, int ih) { return (rps_t)((params & ~0xFF00) | (ih<<8)); }
inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) { static inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) {
return sf | (bw<<3) | (cr<<5) | (nocrc?(1<<7):0) | ((ih&0xFF)<<8); return sf | (bw<<3) | (cr<<5) | (nocrc?(1<<7):0) | ((ih&0xFF)<<8);
} }
#define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8))) #define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8)))
// Two frames with params r1/r2 would interfere on air: same SFx + BWx // Two frames with params r1/r2 would interfere on air: same SFx + BWx
inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; } static inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; }
extern CONST_TABLE(u1_t, _DR2RPS_CRC)[]; extern CONST_TABLE(u1_t, _DR2RPS_CRC)[];
inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); } static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); }
inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); } static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; } static inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; }
inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; } static inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; }
inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? DR_DFLTMIN : dr; } // force into a valid DR static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR
inline bit_t validDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; } // in range static inline bit_t validDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; } // in range
inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
// //
// BEG: Keep in sync with lorabase.hpp // BEG: Keep in sync with lorabase.hpp

View File

@ -71,7 +71,11 @@ static int unlinkjob (osjob_t** pnext, osjob_t* job) {
// clear scheduled job // clear scheduled job
void os_clearCallback (osjob_t* job) { void os_clearCallback (osjob_t* job) {
hal_disableIRQs(); hal_disableIRQs();
unlinkjob(&OS.scheduledjobs, job) || unlinkjob(&OS.runnablejobs, job);
// if it's not in the scheduled jobs, look in the runnable...
if (! unlinkjob(&OS.scheduledjobs, job))
unlinkjob(&OS.runnablejobs, job);
hal_enableIRQs(); hal_enableIRQs();
} }

View File

@ -75,6 +75,14 @@ typedef const u1_t* xref2cu1_t;
typedef u1_t* xref2u1_t; typedef u1_t* xref2u1_t;
typedef s4_t ostime_t; typedef s4_t ostime_t;
// int32_t == s4_t is long on some platforms; and someday
// we will want 64-bit ostime_t. So, we will use a macro for the
// print formatting of ostime_t.
#ifndef LMIC_PRId_ostime_t
# include <inttypes.h>
# define LMIC_PRId_ostime_t PRId32
#endif
#define TYPEDEF_xref2rps_t typedef rps_t* xref2rps_t #define TYPEDEF_xref2rps_t typedef rps_t* xref2rps_t
#define TYPEDEF_xref2rxsched_t typedef rxsched_t* xref2rxsched_t #define TYPEDEF_xref2rxsched_t typedef rxsched_t* xref2rxsched_t
#define TYPEDEF_xref2chnldef_t typedef chnldef_t* xref2chnldef_t #define TYPEDEF_xref2chnldef_t typedef chnldef_t* xref2chnldef_t
@ -83,6 +91,105 @@ typedef s4_t ostime_t;
#define SIZEOFEXPR(x) sizeof(x) #define SIZEOFEXPR(x) sizeof(x)
//----------------------------------------------------------------------------
// Annotations to avoid various "unused" warnings. These must appear as a
// statement in the function body; the macro annotates the variable to quiet
// compiler warnings. The way this is done is compiler-specific, and so these
// definitions are fall-backs, which might be overridden.
//
// Although these are all similar, we don't want extra macro expansions,
// so we define each one explicitly rather than relying on a common macro.
//----------------------------------------------------------------------------
// signal that a parameter is intentionally unused.
#ifndef LMIC_UNREFERENCED_PARAMETER
# define LMIC_UNREFERENCED_PARAMETER(v) do { (void) (v); } while (0)
#endif
// an API parameter is a parameter that is required by an API definition, but
// happens to be unreferenced in this implementation. This is a stronger
// assertion than LMIC_UNREFERENCED_PARAMETER(): this parameter is here
// becuase of an API contract, but we have no use for it in this function.
#ifndef LMIC_API_PARAMETER
# define LMIC_API_PARAMETER(v) do { (void) (v); } while (0)
#endif
// an intentionally-unreferenced variable.
#ifndef LMIC_UNREFERENCED_VARIABLE
# define LMIC_UNREFERENCED_VARIABLE(v) do { (void) (v); } while (0)
#endif
// we have three (!) debug levels (LMIC_DEBUG_LEVEL > 0, LMIC_DEBUG_LEVEL > 1,
// and LMIC_X_DEBUG_LEVEL > 0. In each case we might have parameters or
// or varables that are only refereneced at the target debug level.
// Parameter referenced only if debugging at level > 0.
#ifndef LMIC_DEBUG1_PARAMETER
# if LMIC_DEBUG_LEVEL > 0
# define LMIC_DEBUG1_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_DEBUG1_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if debugging at level > 0
#ifndef LMIC_DEBUG1_VARIABLE
# if LMIC_DEBUG_LEVEL > 0
# define LMIC_DEBUG1_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_DEBUG1_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if debugging at level > 1
#ifndef LMIC_DEBUG2_PARAMETER
# if LMIC_DEBUG_LEVEL > 1
# define LMIC_DEBUG2_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_DEBUG2_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if debugging at level > 1
#ifndef LMIC_DEBUG2_VARIABLE
# if LMIC_DEBUG_LEVEL > 1
# define LMIC_DEBUG2_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_DEBUG2_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if LMIC_X_DEBUG_LEVEL > 0
#ifndef LMIC_X_DEBUG_PARAMETER
# if LMIC_X_DEBUG_LEVEL > 0
# define LMIC_X_DEBUG_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_X_DEBUG_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if LMIC_X_DEBUG_LEVEL > 0
#ifndef LMIC_X_DEBUG_VARIABLE
# if LMIC_X_DEBUG_LEVEL > 0
# define LMIC_X_DEBUG_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_X_DEBUG_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if EV() macro is enabled (which it never is)
// TODO(tmm@mcci.com) take out the EV() framework as it reuqires C++, and
// this code is really C-99 to its bones.
#ifndef LMIC_EV_PARAMETER
# define LMIC_EV_PARAMETER(v) do { (void) (v); } while (0)
#endif
// variable referenced only if EV() macro is defined.
#ifndef LMIC_EV_VARIABLE
# define LMIC_EV_VARIABLE(v) do { (void) (v); } while (0)
#endif
#define ON_LMIC_EVENT(ev) onEvent(ev) #define ON_LMIC_EVENT(ev) onEvent(ev)
#define DECL_ON_LMIC_EVENT void onEvent(ev_t e) #define DECL_ON_LMIC_EVENT void onEvent(ev_t e)
@ -258,7 +365,7 @@ u2_t os_crc16 (xref2cu1_t d, uint len);
// progmem using pgm_read_xx, or accesses memory directly when the // progmem using pgm_read_xx, or accesses memory directly when the
// index is a constant so gcc can optimize it away; // index is a constant so gcc can optimize it away;
#define TABLE_GETTER(postfix, type, pgm_type) \ #define TABLE_GETTER(postfix, type, pgm_type) \
inline type table_get ## postfix(const type *table, size_t index) { \ static inline type table_get ## postfix(const type *table, size_t index) { \
if (__builtin_constant_p(table[index])) \ if (__builtin_constant_p(table[index])) \
return table[index]; \ return table[index]; \
return pgm_read_ ## pgm_type(&table[index]); \ return pgm_read_ ## pgm_type(&table[index]); \
@ -278,13 +385,13 @@ u2_t os_crc16 (xref2cu1_t d, uint len);
// For AVR, store constants in PROGMEM, saving on RAM usage // For AVR, store constants in PROGMEM, saving on RAM usage
#define CONST_TABLE(type, name) const type PROGMEM RESOLVE_TABLE(name) #define CONST_TABLE(type, name) const type PROGMEM RESOLVE_TABLE(name)
#else #else
inline u1_t table_get_u1(const u1_t *table, size_t index) { return table[index]; } static inline u1_t table_get_u1(const u1_t *table, size_t index) { return table[index]; }
inline s1_t table_get_s1(const s1_t *table, size_t index) { return table[index]; } static inline s1_t table_get_s1(const s1_t *table, size_t index) { return table[index]; }
inline u2_t table_get_u2(const u2_t *table, size_t index) { return table[index]; } static inline u2_t table_get_u2(const u2_t *table, size_t index) { return table[index]; }
inline s2_t table_get_s2(const s2_t *table, size_t index) { return table[index]; } static inline s2_t table_get_s2(const s2_t *table, size_t index) { return table[index]; }
inline u4_t table_get_u4(const u4_t *table, size_t index) { return table[index]; } static inline u4_t table_get_u4(const u4_t *table, size_t index) { return table[index]; }
inline s4_t table_get_s4(const s4_t *table, size_t index) { return table[index]; } static inline s4_t table_get_s4(const s4_t *table, size_t index) { return table[index]; }
inline ostime_t table_get_ostime(const ostime_t *table, size_t index) { return table[index]; } static inline ostime_t table_get_ostime(const ostime_t *table, size_t index) { return table[index]; }
// Declare a table // Declare a table
#define CONST_TABLE(type, name) const type RESOLVE_TABLE(name) #define CONST_TABLE(type, name) const type RESOLVE_TABLE(name)

View File

@ -549,7 +549,7 @@ static void txlora () {
u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7
u1_t bw = getBw(LMIC.rps); u1_t bw = getBw(LMIC.rps);
u1_t cr = getCr(LMIC.rps); u1_t cr = getCr(LMIC.rps);
LMIC_DEBUG_PRINTF("%lu: TXMODE, freq=%lu, len=%d, SF=%d, BW=%d, CR=4/%d, IH=%d\n", LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": TXMODE, freq=%"PRIu32", len=%d, SF=%d, BW=%d, CR=4/%d, IH=%d\n",
os_getTime(), LMIC.freq, LMIC.dataLen, sf, os_getTime(), LMIC.freq, LMIC.dataLen, sf,
bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), bw == BW125 ? 125 : (bw == BW250 ? 250 : 500),
cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)),
@ -656,7 +656,7 @@ static void rxlora (u1_t rxmode) {
opmode(OPMODE_RX_SINGLE); opmode(OPMODE_RX_SINGLE);
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
ostime_t now = os_getTime(); ostime_t now = os_getTime();
LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %lu\n", now - LMIC.rxtime); LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime);
#endif #endif
} else { // continous rx (scan or rssi) } else { // continous rx (scan or rssi)
opmode(OPMODE_RX); opmode(OPMODE_RX);
@ -669,7 +669,7 @@ static void rxlora (u1_t rxmode) {
u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7
u1_t bw = getBw(LMIC.rps); u1_t bw = getBw(LMIC.rps);
u1_t cr = getCr(LMIC.rps); u1_t cr = getCr(LMIC.rps);
LMIC_DEBUG_PRINTF("%lu: %s, freq=%lu, SF=%d, BW=%d, CR=4/%d, IH=%d\n", LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": %s, freq=%"PRIu32", SF=%d, BW=%d, CR=4/%d, IH=%d\n",
os_getTime(), os_getTime(),
rxmode == RXMODE_SINGLE ? "RXMODE_SINGLE" : (rxmode == RXMODE_SCAN ? "RXMODE_SCAN" : "UNKNOWN_RX"), rxmode == RXMODE_SINGLE ? "RXMODE_SINGLE" : (rxmode == RXMODE_SCAN ? "RXMODE_SCAN" : "UNKNOWN_RX"),
LMIC.freq, sf, LMIC.freq, sf,
@ -921,7 +921,12 @@ void radio_irq_handler (u1_t dio) {
} }
void radio_irq_handler_v2 (u1_t dio, ostime_t now) { void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
LMIC_API_PARAMETER(dio);
#if CFG_TxContinuousMode #if CFG_TxContinuousMode
// in continuous mode, we don't use the now parameter.
LMIC_UNREFERENCED_PARAMETER(now);
// clear radio IRQ flags // clear radio IRQ flags
writeReg(LORARegIrqFlags, 0xFF); writeReg(LORARegIrqFlags, 0xFF);
u1_t p = readReg(LORARegFifoAddrPtr); u1_t p = readReg(LORARegFifoAddrPtr);
@ -964,7 +969,8 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
LMIC.dataLen = 0; LMIC.dataLen = 0;
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
ostime_t now2 = os_getTime(); ostime_t now2 = os_getTime();
LMIC_DEBUG_PRINTF("rxtimeout: entry: %lu rxtime: %lu entry-rxtime: %lu now-entry: %lu rxtime-txend: %lu\n", entry, LMIC.rxtime, entry - LMIC.rxtime, now2 - entry, LMIC.rxtime-LMIC.txend); LMIC_DEBUG_PRINTF("rxtimeout: entry: %"LMIC_PRId_ostime_t" rxtime: %"LMIC_PRId_ostime_t" entry-rxtime: %"LMIC_PRId_ostime_t" now-entry: %"LMIC_PRId_ostime_t" rxtime-txend: %"LMIC_PRId_ostime_t"\n", entry,
LMIC.rxtime, entry - LMIC.rxtime, now2 - entry, LMIC.rxtime-LMIC.txend);
#endif #endif
} }
// mask all radio IRQs // mask all radio IRQs

View File

@ -29,7 +29,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
[common] [common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c" ; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
release_version = 1.6.54 release_version = 1.6.82
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
debug_level = 0 debug_level = 0
@ -38,28 +38,40 @@ upload_protocol = esptool
;upload_protocol = custom ;upload_protocol = custom
extra_scripts = pre:build.py extra_scripts = pre:build.py
keyfile = ota.conf keyfile = ota.conf
platform_espressif32 = espressif32@1.4.0 platform_espressif32 = espressif32@1.5.0
board_build.partitions = min_spiffs.csv board_build.partitions = min_spiffs.csv
monitor_speed = 115200 monitor_speed = 115200
lib_deps_all = lib_deps_basic =
ArduinoJson@^5.13.1 ArduinoJson@^5.13.1
Time@>=1.5
lib_deps_lora = lib_deps_lora =
; MCCI LoRaWAN LMIC library@^2.2.2 ; MCCI LoRaWAN LMIC library@^2.2.2
lib_deps_display = lib_deps_display =
U8g2@>=2.23.16 U8g2@>=2.25.0
lib_deps_rgbled = lib_deps_rgbled =
SmartLeds@>=1.1.3 SmartLeds@>=1.1.3
lib_deps_gps = lib_deps_gps =
TinyGPSPlus@>=1.0.2 TinyGPSPlus@>=1.0.2
Time@>=1.5 lib_deps_sensors =
Adafruit Unified Sensor@^1.0.2
Adafruit BME680 Library@^1.0.7
lib_deps_all =
${common.lib_deps_basic}
${common.lib_deps_lora}
${common.lib_deps_display}
${common.lib_deps_rgbled}
${common.lib_deps_gps}
${common.lib_deps_sensors}
build_flags = build_flags =
-include "src/hal/${PIOENV}.h" -include src/hal/${PIOENV}.h
-include "src/paxcounter.conf" -include src/paxcounter.conf
-w -w
'-DARDUINO_LMIC_PROJECT_CONFIG_H=../../../src/lmic_config.h' ;'-D ARDUINO_LMIC_PROJECT_CONFIG_H="/$PROJECTSRC_DIR/lmic_config.h"'
'-DCORE_DEBUG_LEVEL=${common.debug_level}' '-D ARDUINO_LMIC_PROJECT_CONFIG_H=../../../src/lmic_config.h'
'-DBINTRAY_PACKAGE="${PIOENV}"' '-D CORE_DEBUG_LEVEL=${common.debug_level}'
'-DPROGVERSION="${common.release_version}"' '-D LOG_LOCAL_LEVEL=${common.debug_level}'
'-D BINTRAY_PACKAGE="${PIOENV}"'
'-D PROGVERSION="${common.release_version}"'
[env:ebox] [env:ebox]
platform = ${common.platform_espressif32} platform = ${common.platform_espressif32}
@ -68,7 +80,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 115200 upload_speed = 115200
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
build_flags = build_flags =
${common.build_flags} ${common.build_flags}
@ -83,8 +95,9 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 115200 upload_speed = 115200
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_rgbled}
build_flags = build_flags =
${common.build_flags} ${common.build_flags}
upload_protocol = ${common.upload_protocol} upload_protocol = ${common.upload_protocol}
@ -98,7 +111,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_display} ${common.lib_deps_display}
build_flags = build_flags =
@ -114,7 +127,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_display} ${common.lib_deps_display}
build_flags = build_flags =
@ -130,7 +143,7 @@ board = ttgo-lora32-v1
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 115200 upload_speed = 115200
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_display} ${common.lib_deps_display}
build_flags = build_flags =
@ -146,7 +159,7 @@ board = ttgo-lora32-v1
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_display} ${common.lib_deps_display}
build_flags = build_flags =
@ -162,7 +175,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_display} ${common.lib_deps_display}
build_flags = build_flags =
@ -178,7 +191,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_display} ${common.lib_deps_display}
build_flags = build_flags =
@ -194,7 +207,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_gps} ${common.lib_deps_gps}
build_flags = build_flags =
@ -211,7 +224,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_rgbled} ${common.lib_deps_rgbled}
build_flags = build_flags =
@ -227,7 +240,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_rgbled} ${common.lib_deps_rgbled}
${common.lib_deps_gps} ${common.lib_deps_gps}
@ -244,7 +257,7 @@ board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_rgbled} ${common.lib_deps_rgbled}
${common.lib_deps_gps} ${common.lib_deps_gps}
@ -262,7 +275,7 @@ board = lolin32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_rgbled} ${common.lib_deps_rgbled}
build_flags = build_flags =
@ -278,7 +291,7 @@ board = lolin32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_rgbled} ${common.lib_deps_rgbled}
build_flags = build_flags =
@ -294,7 +307,7 @@ board = lolin32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps =
${common.lib_deps_all} ${common.lib_deps_basic}
${common.lib_deps_rgbled} ${common.lib_deps_rgbled}
build_flags = build_flags =
${common.build_flags} ${common.build_flags}
@ -308,13 +321,8 @@ framework = arduino
board = featheresp32 board = featheresp32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps = ${common.lib_deps_all}
${common.lib_deps_all} build_flags = ${common.build_flags}
${common.lib_deps_lora}
${common.lib_deps_rgbled}
${common.lib_deps_display}
build_flags =
${common.build_flags}
upload_protocol = ${common.upload_protocol} upload_protocol = ${common.upload_protocol}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
@ -325,14 +333,8 @@ framework = arduino
board = heltec_wifi_lora_32 board = heltec_wifi_lora_32
board_build.partitions = ${common.board_build.partitions} board_build.partitions = ${common.board_build.partitions}
upload_speed = 921600 upload_speed = 921600
lib_deps = lib_deps = ${common.lib_deps_all}
${common.lib_deps_all} build_flags = ${common.build_flags}
${common.lib_deps_lora}
${common.lib_deps_rgbled}
${common.lib_deps_gps}
${common.lib_deps_display}
build_flags =
${common.build_flags}
upload_protocol = ${common.upload_protocol} upload_protocol = ${common.upload_protocol}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}

View File

@ -41,6 +41,11 @@ function Decoder(bytes, port) {
return decode(bytes, [uint8, uint8], ['rssi', 'beacon']); return decode(bytes, [uint8, uint8], ['rssi', 'beacon']);
} }
if (port === 7) {
// BME680 sensor data
return decode(bytes, [temperature, uint16, humidity, uint16], ['temperature', 'pressure', 'humidity', 'air']);
}
} }
@ -132,7 +137,7 @@ var temperature = function (bytes) {
if (isNegative) { if (isNegative) {
t = -t; t = -t;
} }
return t / 1e2; return +(t / 100).toFixed(1);
}; };
temperature.BYTES = 2; temperature.BYTES = 2;
@ -142,7 +147,7 @@ var humidity = function (bytes) {
} }
var h = bytesToInt(bytes); var h = bytesToInt(bytes);
return h / 1e2; return +(h / 100).toFixed(1);
}; };
humidity.BYTES = 2; humidity.BYTES = 2;

View File

@ -40,6 +40,14 @@ function Decoder(bytes, port) {
decoded.rssi = bytes[i++]; decoded.rssi = bytes[i++];
decoded.beacon = bytes[i++]; decoded.beacon = bytes[i++];
} }
if (port === 7) {
var i = 0;
decoded.temperature = ((bytes[i++] << 8) | bytes[i++]);
decoded.pressure = ((bytes[i++] << 8) | bytes[i++]);
decoded.humidity = ((bytes[i++] << 8) | bytes[i++]);
decoded.air = ((bytes[i++] << 8) | bytes[i++]);
}
return decoded; return decoded;

View File

@ -1,10 +1,9 @@
#ifdef HAS_BATTERY_PROBE
#include "globals.h" #include "globals.h"
// Local logging tag // Local logging tag
static const char TAG[] = "main"; static const char TAG[] = "main";
#ifdef HAS_BATTERY_PROBE
esp_adc_cal_characteristics_t *adc_characs = esp_adc_cal_characteristics_t *adc_characs =
(esp_adc_cal_characteristics_t *)calloc( (esp_adc_cal_characteristics_t *)calloc(
1, sizeof(esp_adc_cal_characteristics_t)); 1, sizeof(esp_adc_cal_characteristics_t));
@ -12,8 +11,10 @@ esp_adc_cal_characteristics_t *adc_characs =
static const adc1_channel_t adc_channel = HAS_BATTERY_PROBE; static const adc1_channel_t adc_channel = HAS_BATTERY_PROBE;
static const adc_atten_t atten = ADC_ATTEN_DB_11; static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_unit_t unit = ADC_UNIT_1; static const adc_unit_t unit = ADC_UNIT_1;
#endif
void calibrate_voltage(void) { void calibrate_voltage(void) {
#ifdef HAS_BATTERY_PROBE
// configure ADC // configure ADC
ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12)); ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12));
ESP_ERROR_CHECK(adc1_config_channel_atten(adc_channel, atten)); ESP_ERROR_CHECK(adc1_config_channel_atten(adc_channel, atten));
@ -30,9 +31,11 @@ void calibrate_voltage(void) {
} else { } else {
ESP_LOGI(TAG, "ADC characterization based on default reference voltage"); ESP_LOGI(TAG, "ADC characterization based on default reference voltage");
} }
#endif
} }
uint16_t read_voltage() { uint16_t read_voltage() {
#ifdef HAS_BATTERY_PROBE
// multisample ADC // multisample ADC
uint32_t adc_reading = 0; uint32_t adc_reading = 0;
for (int i = 0; i < NO_OF_SAMPLES; i++) { for (int i = 0; i < NO_OF_SAMPLES; i++) {
@ -47,11 +50,17 @@ uint16_t read_voltage() {
#endif #endif
ESP_LOGD(TAG, "Raw: %d / Voltage: %dmV", adc_reading, voltage); ESP_LOGD(TAG, "Raw: %d / Voltage: %dmV", adc_reading, voltage);
return voltage; return voltage;
#else
return 0;
#endif
} }
bool batt_sufficient() { bool batt_sufficient() {
#ifdef HAS_BATTERY_PROBE
uint16_t volts = read_voltage(); uint16_t volts = read_voltage();
return (( volts < 1000 ) || (volts > OTA_MIN_BATT)); // no battery or battery sufficient return ((volts < 1000) ||
} (volts > OTA_MIN_BATT)); // no battery or battery sufficient
#else
#endif // HAS_BATTERY_PROBE return true;
#endif
}

44
src/bme680read.cpp Normal file
View File

@ -0,0 +1,44 @@
#ifdef HAS_BME
#include "bme680read.h"
// Local logging tag
static const char TAG[] = "main";
#define SEALEVELPRESSURE_HPA (1013.25)
// I2C Bus interface
Adafruit_BME680 bme;
bmeStatus_t bme_status;
void bme_init(void) {
// initialize BME680 sensor using default i2c address 0x77
if (bme.begin(HAS_BME)) {
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
ESP_LOGI(TAG, "BME680 chip found and initialized");
} else
ESP_LOGE(TAG, "BME680 chip not found on i2c bus");
}
bool bme_read(void) {
bool ret = bme.performReading();
if (ret) {
// read current BME data and buffer in global struct
bme_status.temperature = bme.temperature;
bme_status.pressure = (uint16_t)(bme.pressure / 100.0); // convert Pa -> hPa
bme_status.humidity = bme.humidity;
bme_status.gas_resistance = (uint16_t)(bme.gas_resistance / 1000.0); // convert Ohm -> kOhm
ESP_LOGI(TAG, "BME680 sensor data read success");
} else {
ESP_LOGI(TAG, "BME680 sensor read error");
}
return ret;
}
#endif // HAS_BME

View File

@ -10,6 +10,6 @@ void readButton() {
ESP_LOGI(TAG, "Button pressed"); ESP_LOGI(TAG, "Button pressed");
payload.reset(); payload.reset();
payload.addButton(0x01); payload.addButton(0x01);
SendData(BUTTONPORT); SendPayload(BUTTONPORT);
} }
#endif #endif

View File

@ -7,6 +7,9 @@
// Local logging tag // Local logging tag
static const char TAG[] = "main"; static const char TAG[] = "main";
uint32_t userUTCTime; // Seconds since the UTC epoch
unsigned long nextTimeSync = millis();
// do all housekeeping // do all housekeeping
void doHousekeeping() { void doHousekeeping() {
@ -17,7 +20,24 @@ void doHousekeeping() {
if (cfg.runmode == 1) if (cfg.runmode == 1)
do_reset(); do_reset();
// task storage debugging // spi_housekeeping();
lora_housekeeping();
// time sync once per TIME_SYNC_INTERVAL
#ifdef TIME_SYNC_INTERVAL
if (millis() >= nextTimeSync) {
nextTimeSync = millis() + TIME_SYNC_INTERVAL *
60000; // set up next time sync period
do_timesync();
}
#endif
#ifdef HAS_BME
// read BME280 sensor if present
bme_read();
#endif
// task storage debugging //
ESP_LOGD(TAG, "Wifiloop %d bytes left", ESP_LOGD(TAG, "Wifiloop %d bytes left",
uxTaskGetStackHighWaterMark(wifiSwitchTask)); uxTaskGetStackHighWaterMark(wifiSwitchTask));
ESP_LOGD(TAG, "IRQhandler %d bytes left", ESP_LOGD(TAG, "IRQhandler %d bytes left",
@ -25,11 +45,10 @@ void doHousekeeping() {
#ifdef HAS_GPS #ifdef HAS_GPS
ESP_LOGD(TAG, "Gpsloop %d bytes left", uxTaskGetStackHighWaterMark(GpsTask)); ESP_LOGD(TAG, "Gpsloop %d bytes left", uxTaskGetStackHighWaterMark(GpsTask));
#endif #endif
#ifdef HAS_SPI
ESP_LOGD(TAG, "Spiloop %d bytes left", uxTaskGetStackHighWaterMark(SpiTask));
#endif
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
ESP_LOGD(TAG, "LEDloop %d bytes left", uxTaskGetStackHighWaterMark(ledLoopTask)); ESP_LOGD(TAG, "LEDloop %d bytes left",
uxTaskGetStackHighWaterMark(ledLoopTask));
#endif #endif
// read battery voltage into global variable // read battery voltage into global variable
@ -38,24 +57,13 @@ void doHousekeeping() {
ESP_LOGI(TAG, "Measured Voltage: %dmV", batt_voltage); ESP_LOGI(TAG, "Measured Voltage: %dmV", batt_voltage);
#endif #endif
// sync time & date if we have valid gps time
#ifdef HAS_GPS
if (gps.time.isValid()) {
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(),
gps.date.day(), gps.date.month(), gps.date.year());
ESP_LOGI(TAG, "Time synced to %02d:%02d:%02d", hour(), minute(), second());
} else {
ESP_LOGI(TAG, "No valid GPS time");
}
#endif
// check free memory // check free memory
if (esp_get_minimum_free_heap_size() <= MEM_LOW) { if (esp_get_minimum_free_heap_size() <= MEM_LOW) {
ESP_LOGI(TAG, ESP_LOGI(TAG,
"Memory full, counter cleared (heap low water mark = %d Bytes / " "Memory full, counter cleared (heap low water mark = %d Bytes / "
"free heap = %d bytes)", "free heap = %d bytes)",
esp_get_minimum_free_heap_size(), ESP.getFreeHeap()); esp_get_minimum_free_heap_size(), ESP.getFreeHeap());
SendData(COUNTERPORT); // send data before clearing counters SendPayload(COUNTERPORT); // send data before clearing counters
reset_counters(); // clear macs container and reset all counters reset_counters(); // clear macs container and reset all counters
get_salt(); // get new salt for salting hashes get_salt(); // get new salt for salting hashes
@ -81,6 +89,26 @@ void reset_counters() {
macs_ble = 0; macs_ble = 0;
} }
void do_timesync() {
#ifdef TIME_SYNC_INTERVAL
// sync time & date if we have valid gps time
#ifdef HAS_GPS
if (gps.time.isValid()) {
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(),
gps.date.day(), gps.date.month(), gps.date.year());
ESP_LOGI(TAG, "Time synced by GPS to %02d:%02d:%02d", hour(), minute(),
second());
return;
} else {
ESP_LOGI(TAG, "No valid GPS time");
}
#endif // HAS_GPS
// Schedule a network time request at the next possible time
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
ESP_LOGI(TAG, "Network time request scheduled");
#endif // TIME_SYNC_INTERVAL
} // do_timesync()
#ifndef VERBOSE #ifndef VERBOSE
int redirect_log(const char *fmt, va_list args) { int redirect_log(const char *fmt, va_list args) {
// do nothing // do nothing

View File

@ -6,7 +6,6 @@
// Hardware related definitions for ebox ESP32-bit with external connected RFM95 LoRa // Hardware related definitions for ebox ESP32-bit with external connected RFM95 LoRa
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 #define CFG_sx1276_radio 1
#define HAS_LED (23) // blue LED on board #define HAS_LED (23) // blue LED on board

View File

@ -6,7 +6,6 @@
// Hardware related definitions for ebox ESP32-bit with external connected RFM95 LoRa // Hardware related definitions for ebox ESP32-bit with external connected RFM95 LoRa
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 #define CFG_sx1276_radio 1
#define HAS_LED (22) // Green LED on board #define HAS_LED (22) // Green LED on board

View File

@ -6,7 +6,6 @@
// Hardware related definitions for Pycom FiPy Board // Hardware related definitions for Pycom FiPy Board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1272_radio 1 #define CFG_sx1272_radio 1
#define HAS_LED NOT_A_PIN // FiPy has no on board LED, so we use RGB LED #define HAS_LED NOT_A_PIN // FiPy has no on board LED, so we use RGB LED

View File

@ -7,6 +7,13 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa or has no LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa or has no LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI #define HAS_SPI 1 // comment out if device shall not send data via SPI
// pin definitions for SPI slave interface
#define SPI_MOSI GPIO_NUM_23
#define SPI_MISO GPIO_NUM_19
#define SPI_SCLK GPIO_NUM_18
#define SPI_CS GPIO_NUM_5
#define HAS_BME 0x77 // BME680 sensor on I2C bus (SDA=4/SCL=15); comment out if not present
#define CFG_sx1276_radio 1 // select LoRa chip #define CFG_sx1276_radio 1 // select LoRa chip
//#define CFG_sx1272_radio 1 // select LoRa chip //#define CFG_sx1272_radio 1 // select LoRa chip

View File

@ -3,12 +3,11 @@
#include <stdint.h> #include <stdint.h>
//#define HAS_BME 0x77 // BME680 sensor on I2C bus (SDI=21/SCL=22); comment out if not present
// Hardware related definitions for Heltec LoRa-32 Board // Hardware related definitions for Heltec LoRa-32 Board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 #define CFG_sx1276_radio 1
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board
#define HAS_LED (25) // white LED on board #define HAS_LED (25) // white LED on board
#define HAS_BUTTON (0) // button "PROG" on board #define HAS_BUTTON (0) // button "PROG" on board

View File

@ -6,7 +6,6 @@
// Hardware related definitions for Heltec V2 LoRa-32 Board // Hardware related definitions for Heltec V2 LoRa-32 Board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 #define CFG_sx1276_radio 1
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board

View File

@ -7,9 +7,7 @@
#define CFG_sx1272_radio 1 // dummy #define CFG_sx1272_radio 1 // dummy
#define HAS_LED 22 // on board LED on GPIO22 #define HAS_LED (5) // on board LED on GPIO5
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW #define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#endif #endif

View File

@ -18,7 +18,6 @@
#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown #define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 // RFM95 module #define CFG_sx1276_radio 1 // RFM95 module
// Pins for LORA chip SPI interface, reset line and interrupt lines // Pins for LORA chip SPI interface, reset line and interrupt lines

View File

@ -19,7 +19,6 @@
#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown #define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 // RFM95 module #define CFG_sx1276_radio 1 // RFM95 module
// Pins for LORA chip SPI interface, reset line and interrupt lines // Pins for LORA chip SPI interface, reset line and interrupt lines

View File

@ -6,7 +6,6 @@
// Hardware related definitions for Pycom LoPy Board (NOT LoPy4) // Hardware related definitions for Pycom LoPy Board (NOT LoPy4)
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1272_radio 1 #define CFG_sx1272_radio 1
#define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED #define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED
#define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0 #define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0
@ -26,9 +25,9 @@
#define WIFI_ANTENNA 0 // 0 = internal, 1 = external #define WIFI_ANTENNA 0 // 0 = internal, 1 = external
// uncomment this only if your LoPy runs on a PYTRACK BOARD // uncomment this only if your LoPy runs on a PYTRACK BOARD
//#define HAS_GPS 1 #define HAS_GPS 1
//#define GPS_I2C GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21) #define GPS_I2C GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21)
//#define GPS_ADDR 0x10 #define GPS_ADDR 0x10
// uncomment this only if your LoPy runs on a EXPANSION BOARD // uncomment this only if your LoPy runs on a EXPANSION BOARD
//#define HAS_LED (12) // use if LoPy is on Expansion Board, this has a user LED //#define HAS_LED (12) // use if LoPy is on Expansion Board, this has a user LED

View File

@ -6,7 +6,14 @@
// Hardware related definitions for Pycom LoPy4 Board // Hardware related definitions for Pycom LoPy4 Board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI #define HAS_SPI 1 // comment out if device shall not send data via SPI
// pin definitions for local wired SPI slave interface
#define SPI_MOSI GPIO_NUM_22
#define SPI_MISO GPIO_NUM_33
#define SPI_SCLK GPIO_NUM_26
#define SPI_CS GPIO_NUM_36
#define CFG_sx1276_radio 1 #define CFG_sx1276_radio 1
//#define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED //#define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED
#define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0 (P2) #define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0 (P2)

View File

@ -10,6 +10,8 @@
// disable brownout detection (avoid unexpected reset on some boards) // disable brownout detection (avoid unexpected reset on some boards)
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature #define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define HAS_BME 0x76 // BME680 sensor on I2C bus; comment out if not present
#define HAS_LED 13 // ESP32 GPIO12 (pin22) On Board LED #define HAS_LED 13 // ESP32 GPIO12 (pin22) On Board LED
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW #define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
//#define HAS_RGB_LED 13 // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED //#define HAS_RGB_LED 13 // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED
@ -17,7 +19,6 @@
//#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown //#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 // RFM95 module #define CFG_sx1276_radio 1 // RFM95 module
// Pins for LORA chip SPI interface, reset line and interrupt lines // Pins for LORA chip SPI interface, reset line and interrupt lines

View File

@ -6,7 +6,6 @@
// Hardware related definitions for TTGO T-Beam board // Hardware related definitions for TTGO T-Beam board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC #define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define BOARD_HAS_PSRAM // use extra 4MB external RAM #define BOARD_HAS_PSRAM // use extra 4MB external RAM

View File

@ -6,7 +6,6 @@
// Hardware related definitions for TTGOv1 board // Hardware related definitions for TTGOv1 board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 #define CFG_sx1276_radio 1
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board

View File

@ -6,7 +6,6 @@
// Hardware related definitions for TTGO V2 Board // Hardware related definitions for TTGO V2 Board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC #define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C

View File

@ -8,8 +8,9 @@
// This settings are for boards labeled v1.6 on pcb, NOT for v1.5 or older // This settings are for boards labeled v1.6 on pcb, NOT for v1.5 or older
*/ */
#define HAS_BME 0x77 // BME680 sensor on I2C bus (SDI=21/SCL=22); comment out if not present
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC #define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C

View File

@ -10,7 +10,6 @@
*/ */
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC #define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_LED NOT_A_PIN // no usable LED on board #define HAS_LED NOT_A_PIN // no usable LED on board
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature #define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature

View File

@ -35,8 +35,8 @@ void irqHandler(void *pvParameters) {
doHousekeeping(); doHousekeeping();
// is time to send the payload? // is time to send the payload?
if (InterruptStatus & SENDPAYLOAD_IRQ) if (InterruptStatus & SENDCOUNTER_IRQ)
sendPayload(); sendCounter();
} }
vTaskDelete(NULL); // shoud never be reached vTaskDelete(NULL); // shoud never be reached
} }
@ -55,7 +55,7 @@ void IRAM_ATTR homeCycleIRQ() {
} }
void IRAM_ATTR SendCycleIRQ() { void IRAM_ATTR SendCycleIRQ() {
xTaskNotifyFromISR(irqHandlerTask, SENDPAYLOAD_IRQ, eSetBits, NULL); xTaskNotifyFromISR(irqHandlerTask, SENDCOUNTER_IRQ, eSetBits, NULL);
portYIELD_FROM_ISR(); portYIELD_FROM_ISR();
} }

View File

@ -87,9 +87,8 @@ void rgb_set_color(uint16_t hue) {}
#endif #endif
#if (HAS_LED != NOT_A_PIN)
void switch_LED(uint8_t state) { void switch_LED(uint8_t state) {
#if (HAS_LED != NOT_A_PIN)
if (state == LED_ON) { if (state == LED_ON) {
// switch LED on // switch LED on
#ifdef LED_ACTIVE_LOW #ifdef LED_ACTIVE_LOW
@ -105,9 +104,8 @@ void switch_LED(uint8_t state) {
digitalWrite(HAS_LED, LOW); digitalWrite(HAS_LED, LOW);
#endif #endif
} }
}
#endif #endif
}
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)

View File

@ -21,6 +21,9 @@
//#define LMIC_USE_INTERRUPTS //#define LMIC_USE_INTERRUPTS
//time sync via LoRaWAN network, is not yet supported by TTN (LoRaWAN spec v1.0.3)
#define LMIC_ENABLE_DeviceTimeReq 1
// 16 μs per tick // 16 μs per tick
// LMIC requires ticks to be 15.5μs - 100 μs long // LMIC requires ticks to be 15.5μs - 100 μs long
#define US_PER_OSTICK_EXPONENT 4 #define US_PER_OSTICK_EXPONENT 4
@ -38,7 +41,7 @@
// enable more verbose output. Make sure that printf is actually // enable more verbose output. Make sure that printf is actually
// configured (e.g. on AVR it is not by default), otherwise using it can // configured (e.g. on AVR it is not by default), otherwise using it can
// cause crashing. // cause crashing.
//#define LMIC_DEBUG_LEVEL 1 #define LMIC_DEBUG_LEVEL 0
// Enable this to allow using printf() to print to the given serial port // Enable this to allow using printf() to print to the given serial port
// (or any other Print object). This can be easy for debugging. The // (or any other Print object). This can be easy for debugging. The

View File

@ -1,11 +1,11 @@
#ifdef HAS_LORA
// Basic Config // Basic Config
#include "lorawan.h" #include "lorawan.h"
// Local logging Tag // Local logging Tag
static const char TAG[] = "lora"; static const char TAG[] = "lora";
#ifdef HAS_LORA
osjob_t sendjob; osjob_t sendjob;
QueueHandle_t LoraSendQueue; QueueHandle_t LoraSendQueue;
@ -328,10 +328,14 @@ void lora_send(osjob_t *job) {
// waiting for LoRa getting ready // waiting for LoRa getting ready
} else { } else {
if (xQueueReceive(LoraSendQueue, &SendBuffer, (TickType_t)0) == pdTRUE) { if (xQueueReceive(LoraSendQueue, &SendBuffer, (TickType_t)0) == pdTRUE) {
// SendBuffer gets struct MessageBuffer with next payload from queue // SendBuffer now filled with next payload from queue
LMIC_setTxData2(SendBuffer.MessagePort, SendBuffer.Message, if (!LMIC_setTxData2(SendBuffer.MessagePort, SendBuffer.Message,
SendBuffer.MessageSize, (cfg.countermode & 0x02)); SendBuffer.MessageSize, (cfg.countermode & 0x02))) {
ESP_LOGI(TAG, "%d bytes sent to LoRa", SendBuffer.MessageSize); ESP_LOGI(TAG, "%d byte(s) sent to LoRa", SendBuffer.MessageSize);
} else {
ESP_LOGE(TAG, "could not send %d byte(s) to LoRa",
SendBuffer.MessageSize);
}
// sprintf(display_line7, "PACKET QUEUED"); // sprintf(display_line7, "PACKET QUEUED");
} }
} }
@ -341,4 +345,108 @@ void lora_send(osjob_t *job) {
lora_send); lora_send);
} }
#endif // HAS_LORA #endif // HAS_LORA
esp_err_t lora_stack_init() {
#ifndef HAS_LORA
return ESP_OK; // continue main program
#else
LoraSendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t));
if (LoraSendQueue == 0) {
ESP_LOGE(TAG, "Could not create LORA send queue. Aborting.");
return ESP_FAIL;
}
ESP_LOGI(TAG, "LORA send queue created, size %d Bytes",
SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE);
ESP_LOGI(TAG, "Starting LMIC...");
os_init(); // initialize lmic run-time environment on core 1
LMIC_reset(); // initialize lmic MAC
LMIC_setLinkCheckMode(0);
// This tells LMIC to make the receive windows bigger, in case your clock is
// faster or slower. This causes the transceiver to be earlier switched on,
// so consuming more power. You may sharpen (reduce) CLOCK_ERROR_PERCENTAGE
// in src/lmic_config.h if you are limited on battery.
LMIC_setClockError(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100);
// Set the data rate to Spreading Factor 7. This is the fastest supported
// rate for 125 kHz channels, and it minimizes air time and battery power. Set
// the transmission power to 14 dBi (25 mW).
LMIC_setDrTxpow(DR_SF7, 14);
#if defined(CFG_US915) || defined(CFG_au921)
// in the US, with TTN, it saves join time if we start on subband 1 (channels
// 8-15). This will get overridden after the join by parameters from the
// network. If working with other networks or in other regions, this will need
// to be changed.
LMIC_selectSubBand(1);
#endif
if (!LMIC_startJoining()) { // start joining
ESP_LOGI(TAG, "Already joined");
}
return ESP_OK; // continue main program
#endif
}
void lora_enqueuedata(MessageBuffer_t *message) {
// enqueue message in LORA send queue
#ifdef HAS_LORA
BaseType_t ret =
xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0);
if (ret == pdTRUE) {
ESP_LOGI(TAG, "%d bytes enqueued for LORA interface", message->MessageSize);
} else {
ESP_LOGW(TAG, "LORA sendqueue is full");
}
#endif
}
void lora_queuereset(void) {
#ifdef HAS_LORA
xQueueReset(LoraSendQueue);
#endif
}
void lora_housekeeping(void) {
#ifdef HAS_LORA
// ESP_LOGD(TAG, "loraloop %d bytes left",
// uxTaskGetStackHighWaterMark(LoraTask));
#endif
}
void user_request_network_time_callback(void *pVoidUserUTCTime,
int flagSuccess) {
// Explicit conversion from void* to uint32_t* to avoid compiler errors
uint32_t *pUserUTCTime = (uint32_t *)pVoidUserUTCTime;
lmic_time_reference_t lmicTimeReference;
if (flagSuccess != 1) {
ESP_LOGW(TAG, "LoRaWAN network did not answer time request");
return;
}
// Populate lmic_time_reference
flagSuccess = LMIC_getNetworkTimeReference(&lmicTimeReference);
if (flagSuccess != 1) {
ESP_LOGW(TAG, "LoRaWAN time request failed");
return;
}
// Update userUTCTime, considering the difference between the GPS and UTC
// epoch, and the leap seconds
*pUserUTCTime = lmicTimeReference.tNetwork + 315964800;
// Current time, in ticks
ostime_t ticksNow = os_getTime();
// Time when the request was sent, in ticks
ostime_t ticksRequestSent = lmicTimeReference.tLocal;
// Add the delay between the instant the time was transmitted and
// the current time
uint32_t requestDelaySec = osticks2ms(ticksNow - ticksRequestSent) / 1000;
*pUserUTCTime += requestDelaySec;
// Update system time with time read from the network
setTime(*pUserUTCTime);
ESP_LOGI(TAG, "Time synced by LoRa network to %02d:%02d:%02d", hour(),
minute(), second());
}

View File

@ -107,7 +107,7 @@ bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) {
#endif #endif
payload.reset(); payload.reset();
payload.addAlarm(rssi, beaconID); payload.addAlarm(rssi, beaconID);
SendData(BEACONPORT); SendPayload(BEACONPORT);
} }
}; };

View File

@ -55,7 +55,8 @@ uint8_t volatile channel = 0; // channel rotation counter
uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0, uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
batt_voltage = 0; // globals for display batt_voltage = 0; // globals for display
hw_timer_t *channelSwitch, *sendCycle, *homeCycle, *displaytimer; // irq tasks hw_timer_t *channelSwitch = NULL, *sendCycle = NULL, *homeCycle = NULL,
*displaytimer = NULL; // irq tasks
TaskHandle_t irqHandlerTask, wifiSwitchTask; TaskHandle_t irqHandlerTask, wifiSwitchTask;
std::set<uint16_t> macs; // container holding unique MAC adress hashes std::set<uint16_t> macs; // container holding unique MAC adress hashes
@ -157,54 +158,23 @@ void setup() {
strcat_P(features, " GPS"); strcat_P(features, " GPS");
#endif #endif
// initialize gps
#ifdef HAS_BME
strcat_P(features, " BME");
bme_init();
#endif
// initialize LoRa // initialize LoRa
#ifdef HAS_LORA #ifdef HAS_LORA
strcat_P(features, " LORA"); strcat_P(features, " LORA");
LoraSendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t));
if (LoraSendQueue == 0) {
ESP_LOGE(TAG, "Could not create LORA send queue. Aborting.");
exit(0);
} else
ESP_LOGI(TAG, "LORA send queue created, size %d Bytes",
SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE);
ESP_LOGI(TAG, "Starting LMIC...");
os_init(); // initialize lmic run-time environment on core 1
LMIC_reset(); // initialize lmic MAC
LMIC_setLinkCheckMode(0);
// This tells LMIC to make the receive windows bigger, in case your clock is
// faster or slower. This causes the transceiver to be earlier switched on,
// so consuming more power. You may sharpen (reduce) CLOCK_ERROR_PERCENTAGE
// in src/lmic_config.h if you are limited on battery.
LMIC_setClockError(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100);
// Set the data rate to Spreading Factor 7. This is the fastest supported
// rate for 125 kHz channels, and it minimizes air time and battery power. Set
// the transmission power to 14 dBi (25 mW).
LMIC_setDrTxpow(DR_SF7, 14);
#if defined(CFG_US915) || defined(CFG_au921)
// in the US, with TTN, it saves join time if we start on subband 1 (channels
// 8-15). This will get overridden after the join by parameters from the
// network. If working with other networks or in other regions, this will need
// to be changed.
LMIC_selectSubBand(1);
#endif
LMIC_startJoining(); // start joining
#endif #endif
assert(lora_stack_init() == ESP_OK);
// initialize SPI // initialize SPI
#ifdef HAS_SPI #ifdef HAS_SPI
strcat_P(features, " SPI"); strcat_P(features, " SPI");
SPISendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t));
if (SPISendQueue == 0) {
ESP_LOGE(TAG, "Could not create SPI send queue. Aborting.");
exit(0);
} else
ESP_LOGI(TAG, "SPI send queue created, size %d Bytes",
SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE);
#endif #endif
assert(spi_init() == ESP_OK);
#ifdef VENDORFILTER #ifdef VENDORFILTER
strcat_P(features, " OUIFLT"); strcat_P(features, " OUIFLT");
@ -312,17 +282,6 @@ void setup() {
1); // CPU core 1); // CPU core
#endif #endif
#ifdef HAS_SPI
ESP_LOGI(TAG, "Starting SPIloop...");
xTaskCreatePinnedToCore(spi_loop, // task function
"spiloop", // name of task
2048, // stack size of task
(void *)1, // parameter of the task
2, // priority of the task
&SpiTask, // task handle
0); // CPU core
#endif
// start state machine // start state machine
ESP_LOGI(TAG, "Starting IRQ Handler..."); ESP_LOGI(TAG, "Starting IRQ Handler...");
xTaskCreatePinnedToCore(irqHandler, // task function xTaskCreatePinnedToCore(irqHandler, // task function

View File

@ -26,9 +26,6 @@ const BintrayClient bintray(BINTRAY_USER, BINTRAY_REPO, BINTRAY_PACKAGE);
// Connection port (HTTPS) // Connection port (HTTPS)
const int port = 443; const int port = 443;
// Connection timeout
const uint32_t RESPONSE_TIMEOUT_MS = 5000;
// Variables to validate firmware content // Variables to validate firmware content
int volatile contentLength = 0; int volatile contentLength = 0;
bool volatile isValidContentType = false; bool volatile isValidContentType = false;
@ -43,24 +40,13 @@ inline String getHeaderValue(String header, String headerName) {
void start_ota_update() { void start_ota_update() {
/* // check battery status if we can before doing ota
// check battery status if we can before doing ota
#ifdef HAS_BATTERY_PROBE
if (!batt_sufficient()) { if (!batt_sufficient()) {
ESP_LOGW(TAG, "Battery voltage %dmV too low for OTA", batt_voltage); ESP_LOGE(TAG, "Battery voltage %dmV too low for OTA", batt_voltage);
return; return;
} }
#endif
*/
// turn on LED switch_LED(LED_ON);
#if (HAS_LED != NOT_A_PIN)
#ifdef LED_ACTIVE_LOW
digitalWrite(HAS_LED, LOW);
#else
digitalWrite(HAS_LED, HIGH);
#endif
#endif
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
u8x8.begin(); u8x8.begin();
@ -82,86 +68,94 @@ void start_ota_update() {
ESP_LOGI(TAG, "Starting Wifi OTA update"); ESP_LOGI(TAG, "Starting Wifi OTA update");
display(1, "**", WIFI_SSID); display(1, "**", WIFI_SSID);
WiFi.mode(WIFI_AP_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS); WiFi.begin(WIFI_SSID, WIFI_PASS);
int i = WIFI_MAX_TRY; int i = WIFI_MAX_TRY, j = OTA_MAX_TRY;
int ret = 1; // 0 = finished, 1 = retry, -1 = abort
ESP_LOGI(TAG, "Trying to connect to %s", WIFI_SSID);
while (i--) { while (i--) {
ESP_LOGI(TAG, "Trying to connect to %s", WIFI_SSID); if (WiFi.status() == WL_CONNECTED) {
if (WiFi.status() == WL_CONNECTED) // we now have wifi connection and try to do an OTA over wifi update
break; ESP_LOGI(TAG, "Connected to %s", WIFI_SSID);
display(1, "OK", "WiFi connected");
// do a number of tries to update firmware limited by OTA_MAX_TRY
while ((j--) && (ret > 0)) {
ESP_LOGI(TAG, "Starting OTA update, attempt %u of %u", OTA_MAX_TRY - j,
OTA_MAX_TRY);
ret = do_ota_update();
}
goto end;
}
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
WiFi.reconnect();
} }
if (i >= 0) { // wifi did not connect
ESP_LOGI(TAG, "Connected to %s", WIFI_SSID); ESP_LOGI(TAG, "Could not connect to %s", WIFI_SSID);
display(1, "OK", "WiFi connected"); display(1, " E", "no WiFi connect");
do_ota_update(); // gets and flashes new firmware vTaskDelay(5000 / portTICK_PERIOD_MS);
} else {
ESP_LOGI(TAG, "Could not connect to %s, rebooting.", WIFI_SSID);
display(1, " E", "no WiFi connect");
}
end:
switch_LED(LED_OFF);
ESP_LOGI(TAG, "Rebooting to %s firmware", (ret == 0) ? "new" : "current");
display(5, "**", ""); // mark line rebooting display(5, "**", ""); // mark line rebooting
// turn off LED
#if (HAS_LED != NOT_A_PIN)
#ifdef LED_ACTIVE_LOW
digitalWrite(HAS_LED, HIGH);
#else
digitalWrite(HAS_LED, LOW);
#endif
#endif
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
ESP.restart(); ESP.restart();
} // start_ota_update } // start_ota_update
void do_ota_update() { // Reads data vom wifi client and flashes it to ota partition
// returns: 0 = finished, 1 = retry, -1 = abort
int do_ota_update() {
char buf[17]; char buf[17];
bool redirect = true;
size_t written = 0;
// Fetch the latest firmware version // Fetch the latest firmware version
ESP_LOGI(TAG, "Checking latest firmware version on server..."); ESP_LOGI(TAG, "Checking latest firmware version on server");
display(2, "**", "checking version"); display(2, "**", "checking version");
const String latest = bintray.getLatestVersion(); const String latest = bintray.getLatestVersion();
if (latest.length() == 0) { if (latest.length() == 0) {
ESP_LOGI( ESP_LOGI(TAG, "Could not fetch info on latest firmware");
TAG,
"Could not load info about the latest firmware. Rebooting to runmode.");
display(2, " E", "file not found"); display(2, " E", "file not found");
return; return -1;
} else if (version_compare(latest, cfg.version) <= 0) { } else if (version_compare(latest, cfg.version) <= 0) {
ESP_LOGI(TAG, "Current firmware is up to date. Rebooting to runmode."); ESP_LOGI(TAG, "Current firmware is up to date");
display(2, "NO", "no update found"); display(2, "NO", "no update found");
return; return -1;
} }
ESP_LOGI(TAG, "New firmware version v%s available. Downloading...", ESP_LOGI(TAG, "New firmware version v%s available", latest.c_str());
latest.c_str());
display(2, "OK", latest.c_str()); display(2, "OK", latest.c_str());
display(3, "**", ""); display(3, "**", "");
String firmwarePath = bintray.getBinaryPath(latest); String firmwarePath = bintray.getBinaryPath(latest);
if (!firmwarePath.endsWith(".bin")) { if (!firmwarePath.endsWith(".bin")) {
ESP_LOGI(TAG, "Unsupported binary format, OTA update cancelled."); ESP_LOGI(TAG, "Unsupported binary format");
display(3, " E", "file type error"); display(3, " E", "file type error");
return; return -1;
} }
String currentHost = bintray.getStorageHost(); String currentHost = bintray.getStorageHost();
String prevHost = currentHost; String prevHost = currentHost;
WiFiClientSecure client; WiFiClientSecure client;
client.setCACert(bintray.getCertificate(currentHost)); client.setCACert(bintray.getCertificate(currentHost));
// client.setTimeout(RESPONSE_TIMEOUT_MS);
// --> causing error [E][WiFiClient.cpp:236] setSocketOption(): 1006 : 9
// so we unfortunately need patched update.cpp which sets the stream timeout
if (!client.connect(currentHost.c_str(), port)) { if (!client.connect(currentHost.c_str(), port)) {
ESP_LOGI(TAG, "Cannot connect to %s", currentHost.c_str()); ESP_LOGI(TAG, "Cannot connect to %s", currentHost.c_str());
display(3, " E", "connection lost"); display(3, " E", "connection lost");
return; goto abort;
} }
bool redirect = true;
while (redirect) { while (redirect) {
if (currentHost != prevHost) { if (currentHost != prevHost) {
client.stop(); client.stop();
@ -170,7 +164,7 @@ void do_ota_update() {
ESP_LOGI(TAG, "Redirect detected, but cannot connect to %s", ESP_LOGI(TAG, "Redirect detected, but cannot connect to %s",
currentHost.c_str()); currentHost.c_str());
display(3, " E", "server error"); display(3, " E", "server error");
return; goto abort;
} }
} }
@ -183,11 +177,10 @@ void do_ota_update() {
unsigned long timeout = millis(); unsigned long timeout = millis();
while (client.available() == 0) { while (client.available() == 0) {
if (millis() - timeout > RESPONSE_TIMEOUT_MS) { if ((millis() - timeout) > (RESPONSE_TIMEOUT_MS)) {
ESP_LOGI(TAG, "Client Timeout."); ESP_LOGI(TAG, "Client timeout");
display(3, " E", "client timeout"); display(3, " E", "client timeout");
client.stop(); goto abort;
return;
} }
} }
@ -207,13 +200,12 @@ void do_ota_update() {
"firmware flashing"); "firmware flashing");
redirect = false; redirect = false;
} else if (line.indexOf("302") > 0) { } else if (line.indexOf("302") > 0) {
ESP_LOGI(TAG, "Got 302 status code from server. Redirecting to the " ESP_LOGI(TAG, "Got 302 status code from server. Redirecting to "
"new address"); "new address");
redirect = true; redirect = true;
} else { } else {
ESP_LOGI(TAG, "Could not get a valid firmware url."); ESP_LOGI(TAG, "Could not get firmware download URL");
// Unexptected HTTP response. Retry or skip update? goto retry;
redirect = false;
} }
} }
@ -242,80 +234,75 @@ void do_ota_update() {
isValidContentType = true; isValidContentType = true;
} }
} }
} } // while (client.available())
} } // while (redirect)
display(3, "OK", ""); // line download display(3, "OK", ""); // line download
// check whether we have everything for OTA update // check whether we have everything for OTA update
if (contentLength && isValidContentType) { if (!(contentLength && isValidContentType)) {
ESP_LOGI(TAG, "Invalid OTA server response");
size_t written = 0;
if (Update.begin(contentLength)) {
#ifdef HAS_DISPLAY
// register callback function for showing progress while streaming data
Update.onProgress(&show_progress);
#endif
int i = FLASH_MAX_TRY;
while ((i--) && (written != contentLength)) {
ESP_LOGI(TAG,
"Starting OTA update, attempt %u of %u. This will take some "
"time to complete...",
FLASH_MAX_TRY - i, FLASH_MAX_TRY);
display(4, "**", "writing...");
written = Update.writeStream(client);
if (written == contentLength) {
ESP_LOGI(TAG, "Written %u bytes successfully", written);
snprintf(buf, 17, "%ukB Done!", (uint16_t)(written / 1024));
display(4, "OK", buf);
break;
} else {
ESP_LOGI(TAG,
"Written only %u of %u bytes, OTA update attempt cancelled.",
written, contentLength);
}
}
if (Update.end()) {
if (Update.isFinished()) {
ESP_LOGI(
TAG,
"OTA update completed. Rebooting to runmode with new version.");
client.stop();
return;
} else {
ESP_LOGI(TAG, "Something went wrong! OTA update hasn't been finished "
"properly.");
}
} else {
ESP_LOGI(TAG, "An error occurred. Error #: %d", Update.getError());
snprintf(buf, 17, "Error #: %d", Update.getError());
display(4, " E", buf);
}
} else {
ESP_LOGI(TAG, "There isn't enough space to start OTA update");
display(4, " E", "disk full");
client.flush();
}
} else {
ESP_LOGI(TAG,
"There was no valid content in the response from the OTA server!");
display(4, " E", "response error"); display(4, " E", "response error");
client.flush(); goto retry;
} }
ESP_LOGI(TAG,
"OTA update failed. Rebooting to runmode with current version."); #ifdef HAS_LED
#ifndef LED_ACTIVE_LOW
if (!Update.begin(contentLength, U_FLASH, HAS_LED, HIGH)) {
#else
if (!Update.begin(contentLength, U_FLASH, HAS_LED, LOW)) {
#endif
#else
if (!Update.begin(contentLength)) {
#endif
ESP_LOGI(TAG, "Not enough space to start OTA update");
display(4, " E", "disk full");
goto abort;
}
#ifdef HAS_DISPLAY
// register callback function for showing progress while streaming data
Update.onProgress(&show_progress);
#endif
display(4, "**", "writing...");
written = Update.writeStream(client); // this is a blocking call
if (written == contentLength) {
ESP_LOGI(TAG, "Written %u bytes successfully", written);
snprintf(buf, 17, "%ukB Done!", (uint16_t)(written / 1024));
display(4, "OK", buf);
} else {
ESP_LOGI(TAG, "Written only %u of %u bytes, OTA update attempt cancelled",
written, contentLength);
}
if (Update.end()) {
goto finished;
} else {
ESP_LOGI(TAG, "An error occurred. Error#: %d", Update.getError());
snprintf(buf, 17, "Error#: %d", Update.getError());
display(4, " E", buf);
goto retry;
}
finished:
client.stop(); client.stop();
ESP_LOGI(TAG, "OTA update finished");
return 0;
abort:
client.stop();
ESP_LOGI(TAG, "OTA update failed");
return -1;
retry:
return 1;
} // do_ota_update } // do_ota_update
void display(const uint8_t row, const std::string status, void display(const uint8_t row, const std::string status,
const std::string msg) { const std::string msg) {
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
u8x8.setCursor(14, row); u8x8.setCursor(14, row);
u8x8.print((status.substr(0, 2)).c_str()); u8x8.print((status.substr(0, 2)).c_str());
@ -329,45 +316,29 @@ void display(const uint8_t row, const std::string status,
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
// callback function to show download progress while streaming data // callback function to show download progress while streaming data
void show_progress (unsigned long current, unsigned long size) { void show_progress(unsigned long current, unsigned long size) {
char buf[17]; char buf[17];
snprintf(buf, 17, "%-9lu (%3lu%%)", current, current * 100 / size); snprintf(buf, 17, "%-9lu (%3lu%%)", current, current * 100 / size);
display(4, "**", buf); display(4, "**", buf);
} }
#endif #endif
// helper function to compare two versions. Returns 1 if v2 is // helper function to convert strings into lower case
bool comp(char s1, char s2) { return tolower(s1) < tolower(s2); }
// helper function to lexicographically compare two versions. Returns 1 if v2 is
// smaller, -1 if v1 is smaller, 0 if equal // smaller, -1 if v1 is smaller, 0 if equal
int version_compare(const String v1, const String v2) { int version_compare(const String v1, const String v2) {
// vnum stores each numeric part of version
int vnum1 = 0, vnum2 = 0;
// loop until both string are processed if (v1 == v2)
for (int i = 0, j = 0; (i < v1.length() || j < v2.length());) { return 0;
// storing numeric part of version 1 in vnum1
while (i < v1.length() && v1[i] != '.') {
vnum1 = vnum1 * 10 + (v1[i] - '0');
i++;
}
// storing numeric part of version 2 in vnum2 const char *a1 = v1.c_str(), *a2 = v2.c_str();
while (j < v2.length() && v2[j] != '.') {
vnum2 = vnum2 * 10 + (v2[j] - '0');
j++;
}
if (vnum1 > vnum2) if (lexicographical_compare(a1, a1 + strlen(a1), a2, a2 + strlen(a2), comp))
return 1; return -1;
if (vnum2 > vnum1) else
return -1; return 1;
// if equal, reset variables and go for next numeric
// part
vnum1 = vnum2 = 0;
i++;
j++;
}
return 0;
} }
#endif // USE_OTA #endif // USE_OTA

View File

@ -59,6 +59,7 @@
#define LPP1PORT 1 // Port for Cayenne LPP 1.0 dynamic sensor encoding #define LPP1PORT 1 // Port for Cayenne LPP 1.0 dynamic sensor encoding
#define LPP2PORT 2 // Port for Cayenne LPP 2.0 packed sensor encoding #define LPP2PORT 2 // Port for Cayenne LPP 2.0 packed sensor encoding
#define BEACONPORT 6 // Port on which device sends beacon alarms #define BEACONPORT 6 // Port on which device sends beacon alarms
#define BMEPORT 7 // Port on which device sends BME680 sensor data
// Some hardware settings // Some hardware settings
#define RGBLUMINOSITY 30 // RGB LED luminosity [default = 30%] #define RGBLUMINOSITY 30 // RGB LED luminosity [default = 30%]
@ -67,9 +68,13 @@
// OTA settings // OTA settings
#define USE_OTA 1 // Comment out to disable OTA update #define USE_OTA 1 // Comment out to disable OTA update
#define WIFI_MAX_TRY 20 // maximum number of wifi connect attempts for OTA update [default = 20] #define WIFI_MAX_TRY 5 // maximum number of wifi connect attempts for OTA update [default = 20]
#define FLASH_MAX_TRY 3 // maximum number of attempts for writing update binary to flash [default = 3] #define OTA_MAX_TRY 5 // maximum number of attempts for OTA download and write to flash [default = 3]
#define OTA_MIN_BATT 3700 // minimum battery level vor OTA [millivolt] #define OTA_MIN_BATT 3600 // minimum battery level for OTA [millivolt]
#define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds]
// setting for syncing time of node
//#define TIME_SYNC_INTERVAL 60 // sync time each ... minutes [default = 60], comment out means off
// LMIC settings // LMIC settings
// moved to src/lmic_config.h // moved to src/lmic_config.h

View File

@ -75,8 +75,8 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float cputemp,
buffer[cursor++] = (byte)(reset2); buffer[cursor++] = (byte)(reset2);
} }
#ifdef HAS_GPS
void PayloadConvert::addGPS(gpsStatus_t value) { void PayloadConvert::addGPS(gpsStatus_t value) {
#ifdef HAS_GPS
buffer[cursor++] = (byte)((value.latitude & 0xFF000000) >> 24); buffer[cursor++] = (byte)((value.latitude & 0xFF000000) >> 24);
buffer[cursor++] = (byte)((value.latitude & 0x00FF0000) >> 16); buffer[cursor++] = (byte)((value.latitude & 0x00FF0000) >> 16);
buffer[cursor++] = (byte)((value.latitude & 0x0000FF00) >> 8); buffer[cursor++] = (byte)((value.latitude & 0x0000FF00) >> 8);
@ -90,14 +90,32 @@ void PayloadConvert::addGPS(gpsStatus_t value) {
buffer[cursor++] = lowByte(value.hdop); buffer[cursor++] = lowByte(value.hdop);
buffer[cursor++] = highByte(value.altitude); buffer[cursor++] = highByte(value.altitude);
buffer[cursor++] = lowByte(value.altitude); buffer[cursor++] = lowByte(value.altitude);
#endif
} }
#endif
void PayloadConvert::addBME(bmeStatus_t value) {
#ifdef HAS_BME
int16_t temperature = (int16_t)(value.temperature); // float -> int
uint16_t humidity = (uint16_t)(value.humidity); // float -> int
buffer[cursor++] = highByte(temperature);
buffer[cursor++] = lowByte(temperature);
buffer[cursor++] = highByte(value.pressure);
buffer[cursor++] = lowByte(value.pressure);
buffer[cursor++] = highByte(humidity);
buffer[cursor++] = lowByte(humidity);
buffer[cursor++] = highByte(value.gas_resistance);
buffer[cursor++] = lowByte(value.gas_resistance);
#endif
}
void PayloadConvert::addButton(uint8_t value) {
#ifdef HAS_BUTTON #ifdef HAS_BUTTON
void PayloadConvert::addButton(uint8_t value) { buffer[cursor++] = value; } buffer[cursor++] = value;
#endif #endif
}
/* ---------------- packed format with LoRa serialization Encoder ---------- */ /* ---------------- packed format with LoRa serialization Encoder ----------
*/
// derived from // derived from
// https://github.com/thesolarnomad/lora-serialization/blob/master/src/LoraEncoder.cpp // https://github.com/thesolarnomad/lora-serialization/blob/master/src/LoraEncoder.cpp
@ -138,18 +156,29 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float cputemp,
writeUint8(reset2); writeUint8(reset2);
} }
#ifdef HAS_GPS
void PayloadConvert::addGPS(gpsStatus_t value) { void PayloadConvert::addGPS(gpsStatus_t value) {
#ifdef HAS_GPS
writeLatLng(value.latitude, value.longitude); writeLatLng(value.latitude, value.longitude);
writeUint8(value.satellites); writeUint8(value.satellites);
writeUint16(value.hdop); writeUint16(value.hdop);
writeUint16(value.altitude); writeUint16(value.altitude);
#endif
} }
#endif
#ifdef HAS_BUTTON void PayloadConvert::addBME(bmeStatus_t value) {
void PayloadConvert::addButton(uint8_t value) { writeUint8(value); } #ifdef HAS_BME
writeTemperature(value.temperature);
writeUint16(value.pressure);
writeHumidity(value.humidity);
writeUint16(value.gas_resistance);
#endif #endif
}
void PayloadConvert::addButton(uint8_t value) {
#ifdef HAS_BUTTON
writeUint8(value);
#endif
}
void PayloadConvert::intToBytes(uint8_t pos, int32_t i, uint8_t byteSize) { void PayloadConvert::intToBytes(uint8_t pos, int32_t i, uint8_t byteSize) {
for (uint8_t x = 0; x < byteSize; x++) { for (uint8_t x = 0; x < byteSize; x++) {
@ -162,7 +191,7 @@ void PayloadConvert::writeUptime(uint64_t uptime) {
intToBytes(cursor, uptime, 8); intToBytes(cursor, uptime, 8);
} }
void PayloadConvert::writeVersion(char * version) { void PayloadConvert::writeVersion(char *version) {
memcpy(buffer + cursor, version, 10); memcpy(buffer + cursor, version, 10);
cursor += 10; cursor += 10;
} }
@ -212,8 +241,10 @@ void PayloadConvert::writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f,
writeUint8(bitmap); writeUint8(bitmap);
} }
/* ---------------- Cayenne LPP format ---------- */ /* ---------------- Cayenne LPP 2.0 format ---------- */
// http://community.mydevices.com/t/cayenne-lpp-2-0/7510 // see specs http://community.mydevices.com/t/cayenne-lpp-2-0/7510
// PAYLOAD_ENCODER == 3 -> Dynamic Sensor Payload, using channels -> FPort 1
// PAYLOAD_ENCODER == 4 -> Packed Sensor Payload, not using channels -> FPort 2
#elif (PAYLOAD_ENCODER == 3 || PAYLOAD_ENCODER == 4) #elif (PAYLOAD_ENCODER == 3 || PAYLOAD_ENCODER == 4)
@ -221,13 +252,15 @@ void PayloadConvert::addCount(uint16_t value1, uint16_t value2) {
#if (PAYLOAD_ENCODER == 3) #if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_COUNT_WIFI_CHANNEL; buffer[cursor++] = LPP_COUNT_WIFI_CHANNEL;
#endif #endif
buffer[cursor++] = LPP_LUMINOSITY; // workaround, type meter not found? buffer[cursor++] =
LPP_LUMINOSITY; // workaround since cayenne has no data type meter
buffer[cursor++] = highByte(value1); buffer[cursor++] = highByte(value1);
buffer[cursor++] = lowByte(value1); buffer[cursor++] = lowByte(value1);
#if (PAYLOAD_ENCODER == 3) #if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_COUNT_BLE_CHANNEL; buffer[cursor++] = LPP_COUNT_BLE_CHANNEL;
#endif #endif
buffer[cursor++] = LPP_LUMINOSITY; // workaround, type meter not found? buffer[cursor++] =
LPP_LUMINOSITY; // workaround since cayenne has no data type meter
buffer[cursor++] = highByte(value2); buffer[cursor++] = highByte(value2);
buffer[cursor++] = lowByte(value2); buffer[cursor++] = lowByte(value2);
} }
@ -264,17 +297,18 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float celsius,
buffer[cursor++] = LPP_ANALOG_INPUT; buffer[cursor++] = LPP_ANALOG_INPUT;
buffer[cursor++] = highByte(volt); buffer[cursor++] = highByte(volt);
buffer[cursor++] = lowByte(volt); buffer[cursor++] = lowByte(volt);
#endif #endif // HAS_BATTERY_PROBE
#if (PAYLOAD_ENCODER == 3) #if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_TEMP_CHANNEL; buffer[cursor++] = LPP_TEMPERATURE_CHANNEL;
#endif #endif
buffer[cursor++] = LPP_TEMPERATURE; buffer[cursor++] = LPP_TEMPERATURE;
buffer[cursor++] = highByte(temp); buffer[cursor++] = highByte(temp);
buffer[cursor++] = lowByte(temp); buffer[cursor++] = lowByte(temp);
} }
#ifdef HAS_GPS
void PayloadConvert::addGPS(gpsStatus_t value) { void PayloadConvert::addGPS(gpsStatus_t value) {
#ifdef HAS_GPS
int32_t lat = value.latitude / 100; int32_t lat = value.latitude / 100;
int32_t lon = value.longitude / 100; int32_t lon = value.longitude / 100;
int32_t alt = value.altitude * 100; int32_t alt = value.altitude * 100;
@ -287,22 +321,61 @@ void PayloadConvert::addGPS(gpsStatus_t value) {
buffer[cursor++] = (byte)((lat & 0x0000FF)); buffer[cursor++] = (byte)((lat & 0x0000FF));
buffer[cursor++] = (byte)((lon & 0xFF0000) >> 16); buffer[cursor++] = (byte)((lon & 0xFF0000) >> 16);
buffer[cursor++] = (byte)((lon & 0x00FF00) >> 8); buffer[cursor++] = (byte)((lon & 0x00FF00) >> 8);
buffer[cursor++] = (byte)((lon & 0x0000FF)); buffer[cursor++] = (byte)(lon & 0x0000FF);
buffer[cursor++] = (byte)((alt & 0xFF0000) >> 16); buffer[cursor++] = (byte)((alt & 0xFF0000) >> 16);
buffer[cursor++] = (byte)((alt & 0x00FF00) >> 8); buffer[cursor++] = (byte)((alt & 0x00FF00) >> 8);
buffer[cursor++] = (byte)((alt & 0x0000FF)); buffer[cursor++] = (byte)(alt & 0x0000FF);
#endif // HAS_GPS
}
void PayloadConvert::addBME(bmeStatus_t value) {
#ifdef HAS_BME
// data value conversions to meet cayenne data type definition
// 0.1°C per bit => -3276,7 .. +3276,7 °C
int16_t temperature = (int16_t)(value.temperature * 10.0);
// 0.1 hPa per bit => 0 .. 6553,6 hPa
uint16_t pressure = value.pressure * 10;
// 0.5% per bit => 0 .. 128 %C
uint8_t humidity = (uint8_t)(value.humidity * 2.0);
// 0.01 Ohm per bit => 0 .. 655,36 Ohm
uint16_t gas = value.gas_resistance * 100;
#if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_TEMPERATURE_CHANNEL;
#endif
buffer[cursor++] = LPP_TEMPERATURE; // 2 bytes 0.1 °C Signed MSB
buffer[cursor++] = highByte(temperature);
buffer[cursor++] = lowByte(temperature);
#if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_BAROMETER_CHANNEL;
#endif
buffer[cursor++] = LPP_BAROMETER; // 2 bytes 0.1 hPa Unsigned MSB
buffer[cursor++] = highByte(pressure);
buffer[cursor++] = lowByte(pressure);
#if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_HUMIDITY_CHANNEL;
#endif
buffer[cursor++] = LPP_HUMIDITY; // 1 byte 0.5 % Unsigned
buffer[cursor++] = humidity;
#if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_GAS_CHANNEL;
#endif
buffer[cursor++] = LPP_ANALOG_INPUT; // 2 bytes 0.01 Signed
buffer[cursor++] = highByte(gas);
buffer[cursor++] = lowByte(gas);
#endif // HAS_BME
} }
#endif
#ifdef HAS_BUTTON
void PayloadConvert::addButton(uint8_t value) { void PayloadConvert::addButton(uint8_t value) {
#ifdef HAS_BUTTON
#if (PAYLOAD_ENCODER == 3) #if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_BUTTON_CHANNEL; buffer[cursor++] = LPP_BUTTON_CHANNEL;
#endif #endif
buffer[cursor++] = LPP_DIGITAL_INPUT; buffer[cursor++] = LPP_DIGITAL_INPUT;
buffer[cursor++] = value; buffer[cursor++] = value;
#endif // HAS_BUTTON
} }
#endif
#else #else
#error "No valid payload converter defined" #error "No valid payload converter defined"

View File

@ -8,7 +8,10 @@ static const char TAG[] = "main";
// helper function // helper function
void do_reset() { void do_reset() {
ESP_LOGI(TAG, "Remote command: restart device"); ESP_LOGI(TAG, "Remote command: restart device");
if (irqHandlerTask != NULL)
vTaskDelete(irqHandlerTask);
LMIC_shutdown(); LMIC_shutdown();
vTaskDelay(3000 / portTICK_PERIOD_MS);
esp_restart(); esp_restart();
} }
@ -208,7 +211,7 @@ void get_config(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: get device configuration"); ESP_LOGI(TAG, "Remote command: get device configuration");
payload.reset(); payload.reset();
payload.addConfig(cfg); payload.addConfig(cfg);
SendData(CONFIGPORT); SendPayload(CONFIGPORT);
}; };
void get_status(uint8_t val[]) { void get_status(uint8_t val[]) {
@ -222,7 +225,7 @@ void get_status(uint8_t val[]) {
payload.addStatus(voltage, uptime() / 1000, temperatureRead(), payload.addStatus(voltage, uptime() / 1000, temperatureRead(),
ESP.getFreeHeap(), rtc_get_reset_reason(0), ESP.getFreeHeap(), rtc_get_reset_reason(0),
rtc_get_reset_reason(1)); rtc_get_reset_reason(1));
SendData(STATUSPORT); SendPayload(STATUSPORT);
}; };
void get_gps(uint8_t val[]) { void get_gps(uint8_t val[]) {
@ -231,12 +234,23 @@ void get_gps(uint8_t val[]) {
gps_read(); gps_read();
payload.reset(); payload.reset();
payload.addGPS(gps_status); payload.addGPS(gps_status);
SendData(GPSPORT); SendPayload(GPSPORT);
#else #else
ESP_LOGW(TAG, "GPS function not supported"); ESP_LOGW(TAG, "GPS function not supported");
#endif #endif
}; };
void get_bme(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: get bme680 sensor data");
#ifdef HAS_BME
payload.reset();
payload.addBME(bme_status);
SendPayload(BMEPORT);
#else
ESP_LOGW(TAG, "BME680 sensor not supported");
#endif
};
// assign previously defined functions to set of numeric remote commands // assign previously defined functions to set of numeric remote commands
// format: opcode, function, #bytes params, // format: opcode, function, #bytes params,
// flag (true = do make settings persistent / false = don't) // flag (true = do make settings persistent / false = don't)
@ -252,7 +266,8 @@ cmd_t table[] = {
{0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true}, {0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true},
{0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false},
{0x80, get_config, 0, false}, {0x81, get_status, 0, false}, {0x80, get_config, 0, false}, {0x81, get_status, 0, false},
{0x84, get_gps, 0, false}}; {0x84, get_gps, 0, false}, {0x85, get_bme, 0, false},
};
const uint8_t cmdtablesize = const uint8_t cmdtablesize =
sizeof(table) / sizeof(table[0]); // number of commands in command table sizeof(table) / sizeof(table[0]); // number of commands in command table

View File

@ -1,10 +1,10 @@
// Basic Config // Basic Config
#include "globals.h" #include "senddata.h"
// put data to send in RTos Queues used for transmit over channels Lora and SPI // put data to send in RTos Queues used for transmit over channels Lora and SPI
void SendData(uint8_t port) { void SendPayload(uint8_t port) {
MessageBuffer_t SendBuffer; MessageBuffer_t SendBuffer; // contains MessageSize, MessagePort, Message[]
SendBuffer.MessageSize = payload.getSize(); SendBuffer.MessageSize = payload.getSize();
SendBuffer.MessagePort = PAYLOAD_ENCODER <= 2 SendBuffer.MessagePort = PAYLOAD_ENCODER <= 2
@ -12,36 +12,27 @@ void SendData(uint8_t port) {
: (PAYLOAD_ENCODER == 4 ? LPP2PORT : LPP1PORT); : (PAYLOAD_ENCODER == 4 ? LPP2PORT : LPP1PORT);
memcpy(SendBuffer.Message, payload.getBuffer(), payload.getSize()); memcpy(SendBuffer.Message, payload.getBuffer(), payload.getSize());
// enqueue message in LoRa send queue // enqueue message in device's send queues
#ifdef HAS_LORA lora_enqueuedata(&SendBuffer);
if (xQueueSendToBack(LoraSendQueue, (void *)&SendBuffer, (TickType_t)0) == spi_enqueuedata(&SendBuffer);
pdTRUE)
ESP_LOGI(TAG, "%d bytes enqueued to send on LoRa", payload.getSize());
#endif
// enqueue message in SPI send queue } // SendPayload
#ifdef HAS_SPI
if (xQueueSendToBack(SPISendQueue, (void *)&SendBuffer, (TickType_t)0) ==
pdTRUE)
ESP_LOGI(TAG, "%d bytes enqueued to send on SPI", payload.getSize());
#endif
// clear counter if not in cumulative counter mode
if ((port == COUNTERPORT) && (cfg.countermode != 1)) {
reset_counters(); // clear macs container and reset all counters
get_salt(); // get new salt for salting hashes
ESP_LOGI(TAG, "Counter cleared");
}
} // SendData
// interrupt triggered function to prepare payload to send // interrupt triggered function to prepare payload to send
void sendPayload() { void sendCounter() {
// append counter data to payload // append counter data to payload
payload.reset(); payload.reset();
payload.addCount(macs_wifi, cfg.blescan ? macs_ble : 0); payload.addCount(macs_wifi, cfg.blescan ? macs_ble : 0);
// append GPS data, if present // append GPS data, if present
// clear counter if not in cumulative counter mode
if (cfg.countermode != 1) {
reset_counters(); // clear macs container and reset all counters
get_salt(); // get new salt for salting hashes
ESP_LOGI(TAG, "Counter cleared");
}
#ifdef HAS_GPS #ifdef HAS_GPS
// show NMEA data in debug mode, useful for debugging GPS on board // show NMEA data in debug mode, useful for debugging GPS on board
// connection // connection
@ -59,14 +50,18 @@ void sendPayload() {
ESP_LOGD(TAG, "No valid GPS position or GPS data mode disabled"); ESP_LOGD(TAG, "No valid GPS position or GPS data mode disabled");
} }
#endif #endif
SendData(COUNTERPORT); SendPayload(COUNTERPORT);
} // sendpayload()
// if we have MEMS sensor, send sensor data in separate frame
#ifdef HAS_BME
payload.reset();
payload.addBME(bme_status);
SendPayload(BMEPORT);
#endif
} // sendCounter()
void flushQueues() { void flushQueues() {
#ifdef HAS_LORA lora_queuereset();
xQueueReset(LoraSendQueue); spi_queuereset();
#endif
#ifdef HAS_SPI
xQueueReset(SPISendQueue);
#endif
} }

View File

@ -1,72 +0,0 @@
#ifdef HAS_SPI
#include "spisend.h"
// Local logging tag
static const char TAG[] = "main";
MessageBuffer_t SendBuffer;
QueueHandle_t SPISendQueue;
TaskHandle_t SpiTask;
// SPI feed Task
void spi_loop(void *pvParameters) {
uint8_t buf[32];
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
while (1) {
// check if data to send on SPI interface
if (xQueueReceive(SPISendQueue, &SendBuffer, (TickType_t)0) == pdTRUE) {
hal_spi_write(SendBuffer.MessagePort, SendBuffer.Message,
SendBuffer.MessageSize);
ESP_LOGI(TAG, "%d bytes sent to SPI", SendBuffer.MessageSize);
}
// check if command is received on SPI command port, then call interpreter
hal_spi_read(RCMDPORT, buf, 32);
if (buf[0])
rcommand(buf, sizeof(buf));
vTaskDelay(2 / portTICK_PERIOD_MS); // yield to CPU
} // end of infinite loop
vTaskDelete(NULL); // shoud never be reached
} // spi_loop()
// SPI hardware abstraction layer
void hal_spi_init() { SPI.begin(SCK, MISO, MOSI, SS); }
void hal_spi_trx(uint8_t port, uint8_t *buf, int len, uint8_t is_read) {
SPISettings settings(1E6, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(settings);
digitalWrite(SS, 0);
SPI.transfer(port);
for (uint8_t i = 0; i < len; i++) {
uint8_t *p = buf + i;
uint8_t data = is_read ? 0x00 : *p;
data = SPI.transfer(data);
if (is_read)
*p = data;
}
digitalWrite(SS, 1);
SPI.endTransaction();
}
void hal_spi_write(uint8_t port, const uint8_t *buf, int len) {
hal_spi_trx(port, (uint8_t *)buf, len, 0);
}
void hal_spi_read(uint8_t port, uint8_t *buf, int len) {
hal_spi_trx(port, buf, len, 1);
}
#endif // HAS_SPI

176
src/spislave.cpp Normal file
View File

@ -0,0 +1,176 @@
/*
//////////////////////// ESP32-Paxcounter \\\\\\\\\\\\\\\\\\\\\\\\\\
Copyright 2018 Christian Ambach <christian.ambach@deutschebahn.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
NOTICE:
Parts of the source files in this repository are made available under different
licenses. Refer to LICENSE.txt file in repository for more details.
*/
#include "spislave.h"
#include <driver/spi_slave.h>
#include <sys/param.h>
#include <rom/crc.h>
static const char TAG[] = __FILE__;
#define HEADER_SIZE 4
// SPI transaction size needs to be at least 8 bytes and dividable by 4, see
// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/spi_slave.html
#define BUFFER_SIZE \
(MAX(8, HEADER_SIZE + PAYLOAD_BUFFER_SIZE) + \
(PAYLOAD_BUFFER_SIZE % 4 == 0 \
? 0 \
: 4 - MAX(8, HEADER_SIZE + PAYLOAD_BUFFER_SIZE) % 4))
DMA_ATTR uint8_t txbuf[BUFFER_SIZE];
DMA_ATTR uint8_t rxbuf[BUFFER_SIZE];
QueueHandle_t SPISendQueue;
TaskHandle_t spiTask;
void spi_slave_task(void *param) {
while (1) {
MessageBuffer_t msg;
size_t transaction_size;
// clear rx + tx buffers
memset(txbuf, 0, sizeof(txbuf));
memset(rxbuf, 0, sizeof(rxbuf));
// wait until data to send arrivey
if (xQueueReceive(SPISendQueue, &msg, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!");
continue;
}
// fill tx buffer with data to send from queue
uint8_t *messageType = txbuf + 2;
*messageType = msg.MessagePort;
uint8_t *messageSize = txbuf + 3;
*messageSize = msg.MessageSize;
memcpy(txbuf + HEADER_SIZE, &msg.Message, msg.MessageSize);
// calculate crc16 checksum over txbuf and insert checksum at pos 0+1 of txbuf
uint16_t *crc = (uint16_t *)txbuf;
*crc = crc16_be(0, messageType, msg.MessageSize + HEADER_SIZE - 2);
// set length for spi slave driver
transaction_size = HEADER_SIZE + msg.MessageSize;
// SPI transaction size needs to be at least 8 bytes and dividable by 4, see
// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/spi_slave.html
if (transaction_size % 4 != 0) {
transaction_size += (4 - transaction_size % 4);
}
// prepare spi transaction
spi_slave_transaction_t spi_transaction = {0};
spi_transaction.length = transaction_size * 8;
spi_transaction.tx_buffer = txbuf;
spi_transaction.rx_buffer = rxbuf;
// wait until spi master clocks out the data, and read results in rx buffer
ESP_LOGI(TAG, "Prepared SPI transaction for %zu byte(s)", transaction_size);
ESP_LOG_BUFFER_HEXDUMP(TAG, txbuf, transaction_size, ESP_LOG_DEBUG);
esp_err_t ret =
spi_slave_transmit(HSPI_HOST, &spi_transaction, portMAX_DELAY);
ESP_LOG_BUFFER_HEXDUMP(TAG, rxbuf, transaction_size, ESP_LOG_DEBUG);
ESP_LOGI(TAG, "Transaction finished with size %zu bits",
spi_transaction.trans_len);
// check if command was received, then call interpreter with command payload
if ((spi_transaction.trans_len) && ((rxbuf[2]) == RCMDPORT)) {
rcommand(rxbuf + HEADER_SIZE, spi_transaction.trans_len - HEADER_SIZE);
};
}
}
esp_err_t spi_init() {
#ifndef HAS_SPI
return ESP_OK;
#else
SPISendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t));
if (SPISendQueue == 0) {
ESP_LOGE(TAG, "Could not create SPI send queue. Aborting.");
return ESP_FAIL;
}
ESP_LOGI(TAG, "SPI send queue created, size %d Bytes",
SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE);
spi_bus_config_t spi_bus_cfg = {.mosi_io_num = SPI_MOSI,
.miso_io_num = SPI_MISO,
.sclk_io_num = SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 0,
.flags = 0};
spi_slave_interface_config_t spi_slv_cfg = {.spics_io_num = SPI_CS,
.flags = 0,
.queue_size = 1,
.mode = 0,
.post_setup_cb = NULL,
.post_trans_cb = NULL};
// Enable pull-ups on SPI lines so we don't detect rogue pulses when no master
// is connected
gpio_set_pull_mode(SPI_MOSI, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(SPI_SCLK, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(SPI_CS, GPIO_PULLUP_ONLY);
esp_err_t ret =
spi_slave_initialize(HSPI_HOST, &spi_bus_cfg, &spi_slv_cfg, 1);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Starting SPIloop...");
xTaskCreate(spi_slave_task, "spiloop", 4096, (void *)NULL, 2, &spiTask);
} else {
ESP_LOGE(TAG, "SPI interface initialization failed");
}
return ret;
#endif
}
void spi_enqueuedata(MessageBuffer_t *message) {
// enqueue message in SPI send queue
#ifdef HAS_SPI
BaseType_t ret =
xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0);
if (ret == pdTRUE) {
ESP_LOGI(TAG, "%d byte(s) enqueued for SPI interface",
message->MessageSize);
} else {
ESP_LOGW(TAG, "SPI sendqueue is full");
}
#endif
}
void spi_queuereset(void) {
#ifdef HAS_SPI
xQueueReset(SPISendQueue);
#endif
}
void spi_housekeeping(void) {
#ifdef HAS_SPI
ESP_LOGD(TAG, "spiloop %d bytes left", uxTaskGetStackHighWaterMark(spiTask));
#endif
}

View File

@ -1,8 +1,6 @@
/* /*
this file copied from esp32-arduino library and patched, see PR this file copied from esp32-arduino library and patched, see PR
https://github.com/espressif/arduino-esp32/pull/1886 https://github.com/espressif/arduino-esp32/pull/1979
*/ */
#include "update.h" #include "update.h"
@ -95,6 +93,10 @@ void UpdateClass::_reset() {
_progress = 0; _progress = 0;
_size = 0; _size = 0;
_command = U_FLASH; _command = U_FLASH;
if(_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // off
}
} }
bool UpdateClass::canRollBack(){ bool UpdateClass::canRollBack(){
@ -113,12 +115,15 @@ bool UpdateClass::rollBack(){
return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition); return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition);
} }
bool UpdateClass::begin(size_t size, int command) { bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
if(_size > 0){ if(_size > 0){
log_w("already running"); log_w("already running");
return false; return false;
} }
_ledPin = ledPin;
_ledOn = !!ledOn; // 0(LOW) or 1(HIGH)
_reset(); _reset();
_error = 0; _error = 0;
@ -310,7 +315,7 @@ size_t UpdateClass::write(uint8_t *data, size_t len) {
} }
size_t UpdateClass::writeStream(Stream &data) { size_t UpdateClass::writeStream(Stream &data) {
data.setTimeout(20000); data.setTimeout(RESPONSE_TIMEOUT_MS);
size_t written = 0; size_t written = 0;
size_t toRead = 0; size_t toRead = 0;
if(hasError() || !isRunning()) if(hasError() || !isRunning())
@ -323,16 +328,32 @@ size_t UpdateClass::writeStream(Stream &data) {
if (_progress_callback) { if (_progress_callback) {
_progress_callback(0, _size); _progress_callback(0, _size);
} }
if(_ledPin != -1) {
pinMode(_ledPin, OUTPUT);
}
while(remaining()) { while(remaining()) {
toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen)); if(_ledPin != -1) {
digitalWrite(_ledPin, _ledOn); // Switch LED on
}
size_t bytesToRead = SPI_FLASH_SEC_SIZE - _bufferLen;
if(bytesToRead > remaining()) {
bytesToRead = remaining();
}
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
if(toRead == 0) { //Timeout if(toRead == 0) { //Timeout
delay(100); delay(100);
toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen)); toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
if(toRead == 0) { //Timeout if(toRead == 0) { //Timeout
_abort(UPDATE_ERROR_STREAM); _abort(UPDATE_ERROR_STREAM);
return written; return written;
} }
} }
if(_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // Switch LED off
}
_bufferLen += toRead; _bufferLen += toRead;
if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer()) if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer())
return written; return written;