Merge branch 'development' of https://github.com/cyberman54/ESP32-Paxcounter into development

This commit is contained in:
Marius Gripp 2019-10-01 16:29:02 +02:00
commit 014e6cd4a5
53 changed files with 1239 additions and 547 deletions

View File

@ -44,6 +44,7 @@ LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-L
- Pyom: WiPy
- WeMos: LoLin32, LoLin32 Lite, WeMos D32, [Wemos32 Oled](https://www.instructables.com/id/ESP32-With-Integrated-OLED-WEMOSLolin-Getting-Star/)
- Crowdsupply: [TinyPICO](https://www.crowdsupply.com/unexpected-maker/tinypico)
- Generic ESP32
Depending on board hardware following features are supported:
@ -58,7 +59,7 @@ Depending on board hardware following features are supported:
- Real Time Clock (Maxim DS3231 I2C)
- IF482 (serial) and DCF77 (gpio) time telegram generator
- Switch external power / battery
- 64x16 pixel LED Matrix display (similar to [this model](https://www.instructables.com/id/64x16-RED-LED-Marquee/), can be ordered on [Aliexpress](https://www.aliexpress.com/item/P3-75-dot-matrix-led-module-3-75mm-high-clear-top1-for-text-display-304-60mm/32616683948.html))
- LED Matrix display (similar to [this 64x16 model](https://www.instructables.com/id/64x16-RED-LED-Marquee/), can be ordered on [Aliexpress](https://www.aliexpress.com/item/P3-75-dot-matrix-led-module-3-75mm-high-clear-top1-for-text-display-304-60mm/32616683948.html))
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>
@ -155,9 +156,9 @@ If you're using a device with OLED display, or if you add such one to the I2C bu
You can add up to 3 user defined sensors. Insert sensor's payload scheme in [*sensor.cpp*](src/sensor.cpp). Bosch BME280 / BME680 environment sensors are supported. Enable flag *lib_deps_sensors* for your board in [*platformio.ini*](src/platformio.ini) and configure BME in board's hal file before build. If you need Bosch's proprietary BSEC libraray (e.g. to get indoor air quality value from BME680) further enable *build_flags_sensors*, which comes on the price of reduced RAM and increased build size. RTC DS3231, generic serial NMEA GPS, I2C LoPy GPS are supported, and to be configured in board's hal file. See [*generic.h*](src/hal/generic.h) for all options and for proper configuration of BME280/BME680.
Output of user sensor data can be switched by user remote control command 0x13 sent to Port 2.
Output of user sensor data can be switched by user remote control command 0x14 sent to Port 2.
Output of sensor and peripheral data is internally switched by a bitmask register. Default mask (0xFF) can be tailored by editing *cfg.payloadmask* initialization value in [*configmanager.cpp*](src/configmanager.cpp) following this scheme:
Output of sensor and peripheral data is internally switched by a bitmask register. Default mask can be tailored by editing *cfg.payloadmask* initialization value in [*configmanager.cpp*](src/configmanager.cpp) following this scheme:
| Bit | Sensordata |
| --- | ------------- |
@ -168,7 +169,7 @@ Output of sensor and peripheral data is internally switched by a bitmask registe
| 4 | User sensor 1 |
| 5 | User sensor 2 |
| 6 | User sensor 3 |
| 7 | reserved |
| 7 | Batterylevel |
# Time sync
@ -304,21 +305,36 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts.
0 = display off
1 = display on [default]
0x05 set LoRa spread factor
0x05 set LoRa datarate
7 ... 12 [default: 9]
0 ... 15 see LoRaWAN regional parameters for details [default: 5]
Example for EU868:
DataRate Configuration Bit/s
0 LoRa: SF12 / 125 kHz 250
1 LoRa: SF11 / 125 kHz 440
2 LoRa: SF10 / 125 kHz 980
3 LoRa: SF9 / 125 kHz 1760
4 LoRa: SF8 / 125 kHz 3125
5 LoRa: SF7 / 125 kHz 5470
6* LoRa: SF7 / 250 kHz 11000
7* FSK: 50 kbps 50000
8 .. 14 reserved for future use (RFU)
15 ignored (device keeps current setting)
*) not supported by TheThingsNetwork
0x06 set LoRa TXpower
2 ... 15 [default: 15]
0 ... 255 desired TX power in dBm [default: 14]
0x07 set LoRa Adaptive Data Rate mode
0 = ADR off
1 = ADR on [default]
Note: set ADR to off, if device is moving, set to on, if not.
If ADR is set to on, SF value is shown inverted on display.
If ADR is set to off, SF value is shown inverted on display.
0x08 do nothing
@ -382,17 +398,35 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts.
byte 1 = user sensor number (1..3)
byte 2 = sensor mode (0 = disabled / 1 = enabled [default])
0x14 set payload mask
byte 1 = sensor data payload mask (0..255, meaning of bits see above)
0x15 set BME data on/off
0 = BME data off
1 = BME data on, sends BME data on port 7 [default]
0x16 set battery data on/off
0 = battery data off [default]
1 = battery data on, sends voltage on port 8
0x80 get device configuration
Device answers with it's current configuration on Port 3.
0x81 get device status
Device answers with it's current status on Port 2.
Device answers with it's current status on Port 2.
0x83 get battery status
Device answers with battery voltage on Port 8.
0x84 get device GPS status
Device answers with it's current status on Port 4.
Device answers with it's current status on Port 4.
0x85 get BME280 / BME680 sensor data

View File

@ -7,15 +7,15 @@ import os.path
import requests
from os.path import basename
from platformio import util
from SCons.Script import DefaultEnvironment
try:
import configparser
except ImportError:
import ConfigParser as configparser
Import("env")
# get platformio environment variables
env = DefaultEnvironment()
config = configparser.ConfigParser()
config.read("platformio.ini")
@ -66,7 +66,15 @@ myboard = mykeys["board"]
myuploadspeed = mykeys["upload_speed"]
env.Replace(BOARD=myboard)
env.Replace(UPLOAD_SPEED=myuploadspeed)
# re-set partition table
mypartitiontable = config.get("env", "board_build.partitions")
board = env.BoardConfig(myboard)
board.manifest['build']['partitions'] = mypartitiontable
# display target
print('\033[94m' + "TARGET BOARD: " + myboard + " @ " + myuploadspeed + "bps" + '\033[0m')
print('\033[94m' + "Partition table: " + mypartitiontable + '\033[0m')
# parse ota key file
with open(otakeyfile) as myfile:

View File

@ -42,12 +42,16 @@
#define SCREEN_MODE (0x80)
// I2C bus access control
#define I2C_MUTEX_LOCK() (xSemaphoreTake(I2Caccess, pdMS_TO_TICKS(10)) == pdTRUE)
#define I2C_MUTEX_LOCK() \
(xSemaphoreTake(I2Caccess, pdMS_TO_TICKS(10)) == pdTRUE)
#define I2C_MUTEX_UNLOCK() (xSemaphoreGive(I2Caccess))
enum sendprio_t { prio_low, prio_normal, prio_high };
enum timesource_t { _gps, _rtc, _lora, _unsynced };
// Struct holding devices's runtime configuration
typedef struct {
uint8_t lorasf; // 7-12, lora spreadfactor
uint8_t loradr; // 0-15, lora datarate
uint8_t txpower; // 2-15, lora tx power
uint8_t adrmode; // 0=disabled, 1=enabled
uint8_t screensaver; // 0=disabled, 1=enabled
@ -73,6 +77,7 @@ typedef struct {
typedef struct {
uint8_t MessageSize;
uint8_t MessagePort;
sendprio_t MessagePrio;
uint8_t Message[PAYLOAD_BUFFER_SIZE];
} MessageBuffer_t;
@ -95,16 +100,13 @@ typedef struct {
float gas; // raw gas sensor signal
} bmeStatus_t;
enum sendprio_t { prio_low, prio_normal, prio_high };
enum timesource_t { _gps, _rtc, _lora, _unsynced };
extern std::set<uint16_t, std::less<uint16_t>, Mallocator<uint16_t>> macs;
extern std::array<uint64_t, 0xff>::iterator it;
extern std::array<uint64_t, 0xff> beacons;
extern configData_t cfg; // current device configuration
extern char display_line6[], display_line7[]; // screen buffers
extern uint8_t volatile channel; // wifi channel rotation counter
extern configData_t cfg; // current device configuration
extern char lmic_event_msg[]; // display buffer
extern uint8_t volatile channel; // wifi channel rotation counter
extern uint16_t volatile macs_total, macs_wifi, macs_ble,
batt_voltage; // display values
extern bool volatile TimePulseTick; // 1sec pps flag set by GPS or RTC
@ -120,6 +122,7 @@ extern time_t userUTCTime;
#include "led.h"
#include "payload.h"
#include "blescan.h"
#include "power.h"
#if (HAS_GPS)
#include "gpsread.h"
@ -141,10 +144,6 @@ extern time_t userUTCTime;
#include "button.h"
#endif
#ifdef BAT_MEASURE_ADC
#include "battery.h"
#endif
#ifdef HAS_ANTENNA_SWITCH
#include "antenna.h"
#endif

16
include/i2cscan.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _I2CSCAN_H
#define _I2CSCAN_H
#include <Arduino.h>
#define SSD1306_PRIMARY_ADDRESS (0x3D)
#define SSD1306_SECONDARY_ADDRESS (0x3C)
#define BME_PRIMARY_ADDRESS (0x77)
#define BME_SECONDARY_ADDRESS (0x76)
#define AXP192_PRIMARY_ADDRESS (0x34)
#define MCP_24AA02E64_PRIMARY_ADDRESS (0x50)
#define QUECTEL_GPS_PRIMARY_ADDRESS (0x10)
int i2c_scan(void);
#endif

View File

@ -10,12 +10,14 @@
#define UNMASK_IRQ 0x040
#define BME_IRQ 0x080
#define MATRIX_DISPLAY_IRQ 0x100
#define PMU_IRQ 0x200
#include "globals.h"
#include "cyclic.h"
#include "senddata.h"
#include "timekeeper.h"
#include "bmesensor.h"
#include "power.h"
void irqHandler(void *pvParameters);
void mask_user_IRQ();
@ -33,5 +35,9 @@ void IRAM_ATTR MatrixDisplayIRQ();
void IRAM_ATTR ButtonIRQ();
#endif
#ifdef HAS_PMU
void IRAM_ATTR PMUIRQ();
#endif
#endif

View File

@ -13,5 +13,6 @@ void refreshTheMatrixDisplay(bool nextPage = false);
void DrawNumber(String strNum, uint8_t iDotPos = 0);
uint8_t GetCharFromFont(char cChar);
uint8_t GetCharWidth(char cChar);
void ScrollLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows);
#endif

View File

@ -4,7 +4,7 @@
#include "globals.h"
#include "rcommand.h"
#include "timekeeper.h"
#if(TIME_SYNC_LORASERVER)
#if (TIME_SYNC_LORASERVER)
#include "timesync.h"
#endif
@ -21,7 +21,14 @@
#endif
extern QueueHandle_t LoraSendQueue;
extern TaskHandle_t lmicTask;
extern TaskHandle_t lmicTask, lorasendTask;
// table of LORAWAN MAC commands
typedef struct {
const uint8_t opcode;
const char cmdname[20];
const uint8_t params;
} mac_t;
esp_err_t lora_stack_init();
void lmictask(void *pvParameters);
@ -33,10 +40,19 @@ void os_getDevKey(u1_t *buf);
void os_getArtEui(u1_t *buf);
void os_getDevEui(u1_t *buf);
void showLoraKeys(void);
void switch_lora(uint8_t sf, uint8_t tx);
void lora_send(osjob_t *job);
void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio);
void lora_send(void *pvParameters);
void lora_enqueuedata(MessageBuffer_t *message);
void lora_queuereset(void);
void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
size_t nMsg);
void myTxCallback(void *pUserData, int fSuccess);
void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, const mac_t table[],
const uint8_t tablesize);
uint8_t getBattLevel(void);
const char *getSfName(rps_t rps);
const char *getBwName(rps_t rps);
const char *getCrName(rps_t rps);
#if (TIME_SYNC_LORAWAN)
void user_request_network_time_callback(void *pVoidUserUTCTime,
int flagSuccess);

View File

@ -1,12 +1,14 @@
#ifndef _MAIN_H
#define _MAIN_H
#include <esp_spi_flash.h> // needed for reading ESP32 chip attributes
#include <esp_event_loop.h> // needed for Wifi event handler
#include <esp_spi_flash.h> // needed for reading ESP32 chip attributes
#include <esp_event_loop.h> // needed for Wifi event handler
#include <esp32-hal-timer.h> // needed for timers
#include <esp_coexist.h> // needed for showing coex sw version
#include <esp_coexist.h> // needed for showing coex sw version
#include "globals.h"
#include "power.h"
#include "i2cscan.h"
#include "blescan.h"
#include "wifiscan.h"
#include "configmanager.h"

View File

@ -4,7 +4,6 @@
#ifdef USE_OTA
#include "globals.h"
#include "battery.h"
#include <Update.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>

View File

@ -1,8 +1,10 @@
#ifndef _BATTERY_H
#define _BATTERY_H
#ifndef _POWER_H
#define _POWER_H
#include <Arduino.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>
#include "i2cscan.h"
#define DEFAULT_VREF 1100 // tbd: use adc2_vref_to_gpio() for better estimate
#define NO_OF_SAMPLES 64 // we do some multisampling to get better values
@ -11,4 +13,12 @@ uint16_t read_voltage(void);
void calibrate_voltage(void);
bool batt_sufficient(void);
#endif
#ifdef HAS_PMU
#include <axp20x.h>
void power_event_IRQ(void);
void AXP192_power(bool on);
void AXP192_init(void);
void AXP192_showstatus(void);
#endif // HAS_PMU
#endif

View File

@ -19,11 +19,11 @@
typedef struct {
const uint8_t opcode;
void (*func)(uint8_t []);
uint8_t params;
const uint8_t params;
const bool store;
} cmd_t;
void rcommand(uint8_t cmd[], uint8_t cmdlength);
void rcommand(const uint8_t cmd[], const uint8_t cmdlength);
void do_reset();
#endif

View File

@ -10,7 +10,7 @@
extern Ticker sendcycler;
void SendPayload(uint8_t port, sendprio_t prio);
void sendCounter(void);
void sendData(void);
void checkSendQueues(void);
void flushQueues();
void sendcycle(void);

View File

@ -28,7 +28,7 @@ licenses. Refer to LICENSE.txt file in repository for more details.
esp_err_t spi_init();
void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio);
void spi_enqueuedata(MessageBuffer_t *message);
void spi_queuereset();
#endif // _SPISLAVE_H

View File

@ -27,6 +27,7 @@ void timeSync(void);
uint8_t timepulse_init(void);
time_t timeIsValid(time_t const t);
void calibrateTime(void);
void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, timesource_t mytimesource);
time_t compiledUTC(void);
TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
int8_t rxPin, int8_t txPins);

View File

@ -12,9 +12,8 @@
void timesync_init(void);
void send_timesync_req(void);
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len);
int recv_timesync_ans(const uint8_t buf[], uint8_t buf_len);
void process_timesync_req(void *taskparameter);
void store_time_sync_req(uint32_t t_millisec);
void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, timesource_t timesource);
#endif
#endif

View File

@ -19,6 +19,7 @@ halfile = generic.h
;halfile = ttgov21new.h
;halfile = ttgofox.h
;halfile = ttgobeam.h
;halfile = ttgobeam10.h
;halfile = fipy.h
;halfile = lopy.h
;halfile = lopy4.h
@ -28,6 +29,8 @@ halfile = generic.h
;halfile = wemos32oled.h
;halfile = wemos32matrix.h
;halfile = octopus32.h
;halfile = tinypico.h
;halfile = tinypicomatrix.h
[platformio]
; upload firmware to board with usb cable
@ -40,7 +43,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I
[common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
release_version = 1.7.841
release_version = 1.8.34
; 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
debug_level = 3
@ -52,14 +55,13 @@ platform_espressif32 = espressif32@1.9.0
monitor_speed = 115200
upload_speed = 115200
lib_deps_lora =
;MCCI LoRaWAN LMIC library@2.3.2
https://github.com/mcci-catena/arduino-lmic.git
MCCI LoRaWAN LMIC library@>=3.0.99
lib_deps_display =
U8g2@>=2.26.13
lib_deps_matrix_display =
https://github.com/Seeed-Studio/Ultrathin_LED_Matrix.git
lib_deps_rgbled =
SmartLeds@>=1.1.5
SmartLeds@>=1.1.6
lib_deps_gps =
1655@>=1.0.2 ;TinyGPSPlus by Mikal Hart
lib_deps_sensors =
@ -70,6 +72,8 @@ lib_deps_basic =
76@>=1.2.2 ;Timezone by Jack Christensen
274@>=2.3.3 ;RTC by Michael Miller
SimpleButton
;AXP202X_Library@^1.0.1
https://github.com/lewisxhe/AXP202X_Library.git#8045ddf
lib_deps_all =
${common.lib_deps_basic}
${common.lib_deps_lora}
@ -115,6 +119,4 @@ upload_protocol = esptool
upload_protocol = esptool
build_type = debug
platform = https://github.com/platformio/platform-espressif32.git#develop
platform_packages =
; use upstream Git version
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git

View File

@ -37,7 +37,7 @@ function Decoder(bytes, port) {
if (port === 3) {
// device config data
return decode(bytes, [uint8, uint8, int16, uint8, uint8, uint8, uint8, bitmap1, bitmap2, version], ['lorasf', 'txpower', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags', 'payloadmask', 'version']);
return decode(bytes, [uint8, uint8, int16, uint8, uint8, uint8, uint8, bitmap1, bitmap2, version], ['loradr', 'txpower', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags', 'payloadmask', 'version']);
}
if (port === 4) {

View File

@ -1,69 +0,0 @@
#include "globals.h"
// Local logging tag
static const char TAG[] = __FILE__;
#ifdef BAT_MEASURE_ADC
esp_adc_cal_characteristics_t *adc_characs =
(esp_adc_cal_characteristics_t *)calloc(
1, sizeof(esp_adc_cal_characteristics_t));
static const adc1_channel_t adc_channel = BAT_MEASURE_ADC;
static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_unit_t unit = ADC_UNIT_1;
#endif
void calibrate_voltage(void) {
#ifdef BAT_MEASURE_ADC
// configure ADC
ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12));
ESP_ERROR_CHECK(adc1_config_channel_atten(adc_channel, atten));
// calibrate ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(
unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_characs);
// show ADC characterization base
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
ESP_LOGI(TAG,
"ADC characterization based on Two Point values stored in eFuse");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
ESP_LOGI(TAG,
"ADC characterization based on reference voltage stored in eFuse");
} else {
ESP_LOGI(TAG, "ADC characterization based on default reference voltage");
}
#endif
}
uint16_t read_voltage() {
#ifdef BAT_MEASURE_ADC
// multisample ADC
uint32_t adc_reading = 0;
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_reading += adc1_get_raw(adc_channel);
}
adc_reading /= NO_OF_SAMPLES;
// Convert ADC reading to voltage in mV
uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_characs);
#ifdef BAT_VOLTAGE_DIVIDER
voltage *= BAT_VOLTAGE_DIVIDER;
#endif
#ifdef BAT_MEASURE_EN // turn ext. power off
digitalWrite(EXT_POWER_SW, EXT_POWER_OFF);
#endif
return (uint16_t)voltage;
#else
return 0;
#endif
}
bool batt_sufficient() {
#ifdef BAT_MEASURE_ADC
uint16_t volts = read_voltage();
return ((volts < 1000) ||
(volts > OTA_MIN_BATT)); // no battery or battery sufficient
#else
return true;
#endif
}

View File

@ -143,7 +143,9 @@ int checkIaqSensorStatus(void) {
// store current BME sensor data in struct
void bme_storedata(bmeStatus_t *bme_store) {
if (I2C_MUTEX_LOCK()) { // block i2c bus access
if ((cfg.payloadmask & MEMS_DATA) &&
(I2C_MUTEX_LOCK())) { // block i2c bus access
#ifdef HAS_BME680
if (iaqSensor.run()) { // if new data is available

View File

@ -8,16 +8,21 @@ static const char TAG[] = "flash";
nvs_handle my_handle;
esp_err_t err;
#define PAYLOADMASK \
((GPS_DATA | ALARM_DATA | MEMS_DATA | COUNT_DATA | SENSOR1_DATA | \
SENSOR2_DATA | SENSOR3_DATA) & \
~BATT_DATA)
// populate cfg vars with factory settings
void defaultConfig() {
cfg.lorasf = LORASFDEFAULT; // 7-12, initial lora sf, see pacounter.conf
cfg.txpower = 15; // 2-15, lora tx power
cfg.adrmode = 1; // 0=disabled, 1=enabled
cfg.screensaver = 0; // 0=disabled, 1=enabled
cfg.screenon = 1; // 0=disabled, 1=enabled
cfg.countermode = 0; // 0=cyclic, 1=cumulative, 2=cyclic confirmed
cfg.rssilimit = 0; // threshold for rssilimiter, negative value!
cfg.sendcycle = SENDCYCLE; // payload send cycle [seconds/2]
cfg.loradr = LORADRDEFAULT; // 0-15, lora datarate, see pacounter.conf
cfg.txpower = LORATXPOWDEFAULT; // 0-15, lora tx power
cfg.adrmode = 1; // 0=disabled, 1=enabled
cfg.screensaver = 0; // 0=disabled, 1=enabled
cfg.screenon = 1; // 0=disabled, 1=enabled
cfg.countermode = 0; // 0=cyclic, 1=cumulative, 2=cyclic confirmed
cfg.rssilimit = 0; // threshold for rssilimiter, negative value!
cfg.sendcycle = SENDCYCLE; // payload send cycle [seconds/2]
cfg.wifichancycle =
WIFI_CHANNEL_SWITCH_INTERVAL; // wifi channel switch cycle [seconds/100]
cfg.blescantime =
@ -29,7 +34,7 @@ void defaultConfig() {
cfg.rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%)
cfg.monitormode = 0; // 0=disabled, 1=enabled
cfg.runmode = 0; // 0=normal, 1=update
cfg.payloadmask = 0xFF; // all payload switched on
cfg.payloadmask = PAYLOADMASK; // all payload switched on
cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE] = {
0}; // init BSEC state for BME680 sensor
@ -92,9 +97,9 @@ void saveConfig() {
strcmp(storedversion, cfg.version) != 0)
nvs_set_str(my_handle, "version", cfg.version);
if (nvs_get_i8(my_handle, "lorasf", &flash8) != ESP_OK ||
flash8 != cfg.lorasf)
nvs_set_i8(my_handle, "lorasf", cfg.lorasf);
if (nvs_get_i8(my_handle, "loradr", &flash8) != ESP_OK ||
flash8 != cfg.loradr)
nvs_set_i8(my_handle, "loradr", cfg.loradr);
if (nvs_get_i8(my_handle, "txpower", &flash8) != ESP_OK ||
flash8 != cfg.txpower)
@ -217,11 +222,11 @@ void loadConfig() {
ESP_LOGI(TAG, "bsecstate = %d", cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE]);
};
if (nvs_get_i8(my_handle, "lorasf", &flash8) == ESP_OK) {
cfg.lorasf = flash8;
ESP_LOGI(TAG, "lorasf = %d", flash8);
if (nvs_get_i8(my_handle, "loradr", &flash8) == ESP_OK) {
cfg.loradr = flash8;
ESP_LOGI(TAG, "loradr = %d", flash8);
} else {
ESP_LOGI(TAG, "lorasf set to default %d", cfg.lorasf);
ESP_LOGI(TAG, "loradr set to default %d", cfg.loradr);
saveConfig();
}

View File

@ -30,6 +30,9 @@ void doHousekeeping() {
#if (HAS_LORA)
ESP_LOGD(TAG, "LMiCtask %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(lmicTask), eTaskGetState(lmicTask));
ESP_LOGD(TAG, "Lorasendtask %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(lorasendTask),
eTaskGetState(lorasendTask));
#endif
#if (HAS_GPS)
ESP_LOGD(TAG, "Gpsloop %d bytes left | Taskstate = %d",
@ -52,9 +55,15 @@ void doHousekeeping() {
#endif
// read battery voltage into global variable
#ifdef BAT_MEASURE_ADC
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
batt_voltage = read_voltage();
ESP_LOGI(TAG, "Voltage: %dmV", batt_voltage);
if (batt_voltage == 0xffff)
ESP_LOGI(TAG, "Battery: external power");
else
ESP_LOGI(TAG, "Battery: %dmV", batt_voltage);
#ifdef HAS_PMU
AXP192_showstatus();
#endif
#endif
// display BME680/280 sensor data

View File

@ -10,15 +10,17 @@ Display-Mask (128 x 64 pixel):
0|PAX:aabbccddee
1|PAX:aabbccddee
2|B:a.bcV Sats:ab
3|BLTH:abcde SF:ab
3|BLTH:abcde SFab
4|WIFI:abcde ch:ab
5|RLIM:abcd abcdKB
6|xxxxxxxxxxxxxxxx
6|20:27:00* 27.Feb
7|yyyyyyyyyyyyyyab
line 6: x = Text for LORA status OR time/date
line 7: y = Text for LMIC status; ab = payload queue
line 6: * = char {L|G|R|?} indicates time source,
inverse = clock controller is active,
pulsed = pps input signal is active
line 7: y = LMIC event message; ab = payload queue length
*/
@ -30,19 +32,6 @@ line 7: y = Text for LMIC status; ab = payload queue
HAS_DISPLAY u8x8(MY_OLED_RST, MY_OLED_SCL, MY_OLED_SDA);
// helper string for converting LoRa spread factor values
#if defined(CFG_eu868)
const char lora_datarate[] = {"1211100908077BFSNA"};
#elif defined(CFG_us915)
const char lora_datarate[] = {"100908078CNA121110090807"};
#elif defined(CFG_as923)
const char lora_datarate[] = {"1211100908077BFSNA"};
#elif defined(CFG_au921)
const char lora_datarate[] = {"1211100908078CNA1211109C8C7C"};
#elif defined(CFG_in866)
const char lora_datarate[] = {"121110090807FSNA"};
#endif
// helper arry for converting month values to text
const char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
@ -188,9 +177,12 @@ void draw_page(time_t t, uint8_t page) {
case 0:
// update Battery status (line 2)
#ifdef BAT_MEASURE_ADC
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
u8x8.setCursor(0, 2);
u8x8.printf("B:%.2fV", batt_voltage / 1000.0);
if (batt_voltage == 0xffff)
u8x8.printf("B:USB ");
else
u8x8.printf("B:%.2fV", batt_voltage / 1000.0);
#endif
// update GPS status (line 2)
@ -215,13 +207,11 @@ void draw_page(time_t t, uint8_t page) {
#endif
#if (HAS_LORA)
u8x8.setCursor(11, 3);
u8x8.printf("SF:");
if (cfg.adrmode) // if ADR=on then display SF value inverse
u8x8.setCursor(12, 3);
if (!cfg.adrmode) // if ADR=off then display SF value inverse
u8x8.setInverseFont(1);
u8x8.printf("%c%c", lora_datarate[LMIC.datarate * 2],
lora_datarate[LMIC.datarate * 2 + 1]);
if (cfg.adrmode) // switch off inverse if it was turned on
u8x8.printf("%-4s", getSfName(updr2rps(LMIC.datarate)));
if (!cfg.adrmode) // switch off inverse if it was turned on
u8x8.setInverseFont(0);
#endif // HAS_LORA
@ -254,17 +244,13 @@ void draw_page(time_t t, uint8_t page) {
#endif // HAS_DCF77 || HAS_IF482
if (timeSource != _unsynced)
u8x8.printf(" %2d.%3s", day(t), printmonth[month(t)]);
#else // update LoRa status display
#if (HAS_LORA)
u8x8.printf("%-16s", display_line6);
#endif
#endif // TIME_SYNC_INTERVAL
#if (HAS_LORA)
// line 7: update LMiC event display
u8x8.setCursor(0, 7);
u8x8.printf("%-14s", display_line7);
u8x8.printf("%-14s", lmic_event_msg);
// update LoRa send queue display
msgWaiting = uxQueueMessagesWaiting(LoraSendQueue);
@ -274,7 +260,6 @@ void draw_page(time_t t, uint8_t page) {
u8x8.printf("%-2s", msgWaiting == SEND_QUEUE_SIZE ? "<>" : buff);
} else
u8x8.printf(" ");
#endif // HAS_LORA
break; // page0

View File

@ -90,13 +90,12 @@ time_t fetch_gpsTime(uint16_t *msec) {
// poll NMEA $GPZDA sentence
#ifdef GPS_SERIAL
GPS_Serial.print(ZDA_Request);
// wait for gps NMEA answer
vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL));
#elif defined GPS_I2C
Wire.print(ZDA_Request);
#endif
// wait for gps NMEA answer
//vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL));
// did we get a current time?
if (gpstime.isUpdated() && gpstime.isValid()) {
@ -104,7 +103,8 @@ time_t fetch_gpsTime(uint16_t *msec) {
String rawtime = gpstime.value();
uint32_t time_bcd = rawtime.toFloat() * 100;
uint32_t delay_ms = gpstime.age() + nmea_txDelay_ms + NMEA_COMPENSATION_FACTOR;
uint32_t delay_ms =
gpstime.age() + nmea_txDelay_ms + NMEA_COMPENSATION_FACTOR;
uint8_t year =
CalendarYrToTm(gps.date.year()); // year offset from 1970 in microTime.h
@ -138,7 +138,7 @@ void gps_loop(void *pvParameters) {
while (1) {
if (cfg.payloadmask && GPS_DATA) {
if (cfg.payloadmask & GPS_DATA) {
#ifdef GPS_SERIAL
// feed GPS decoder with serial NMEA data from GPS device
while (GPS_Serial.available()) {
@ -153,10 +153,10 @@ void gps_loop(void *pvParameters) {
#endif
} // if
// show NMEA data in verbose mode, useful for debugging GPS
ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix: %u",
gps.passedChecksum(), gps.failedChecksum(),
gps.sentencesWithFix());
// show NMEA data in verbose mode, useful for debugging GPS, bu tvery noisy
//ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix: %u",
// gps.passedChecksum(), gps.failedChecksum(),
// gps.sentencesWithFix());
delay(2); // yield to CPU

View File

@ -14,7 +14,7 @@
#define CFG_sx1276_radio 1
#define HAS_LED (22) // Green LED on board
#define HAS_RGB_LED (2) // WS2812B RGB LED on board
#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_2) // WS2812B RGB LED on board
#define HAS_BUTTON (0) // button "FLASH" on board
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature

View File

@ -13,7 +13,7 @@
#define CFG_sx1272_radio 1
#define HAS_LED NOT_A_PIN // FiPy has no on board LED, so we use RGB LED
#define HAS_RGB_LED GPIO_NUM_0 // WS2812B RGB LED on GPIO0
#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0
#define BOARD_HAS_PSRAM // use extra 4MB extern RAM
// Pins for LORA chip SPI interface, reset line and interrupt lines

View File

@ -51,7 +51,7 @@
#define HAS_LED (21) // on board LED
#define HAS_BUTTON (39) // on board button
#define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0
#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0
// GPS settings
#define HAS_GPS 1 // use on board GPS

View File

@ -20,8 +20,13 @@
#define HAS_LED LED_BUILTIN // white LED on board
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board
// caveat: activating ADC2 conflicts with Wifi in current arduino-esp32
// see https://github.com/espressif/arduino-esp32/issues/3222
// thus we must waiver of battery monitoring
//#define BAT_MEASURE_ADC ADC2_GPIO13_CHANNEL // battery probe GPIO pin
//#define BAT_VOLTAGE_DIVIDER 4 // voltage divider 220k/100k on board
//#define BAT_MEASURE_ADC_UNIT 2 // ADC 2
//#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 220k/100k on board
#define EXT_POWER_SW Vext // switches battery power, Vext control 0 = on / 1 = off
#define EXT_POWER_ON 0
//#define EXT_POWER_OFF 1

View File

@ -17,7 +17,7 @@
//#define DISPLAY_FLIP 1 // uncomment this for rotated display
#define HAS_LED 22 // ESP32 GPIO12 (pin22) On Board LED
#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 SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_13) // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED
#define HAS_BUTTON 15 // ESP32 GPIO15 (pin15) Button is on the LoraNode32 shield
#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown

View File

@ -18,7 +18,7 @@
#define HAS_LED NOT_A_PIN // Led os on same pin as Lora SS pin, to avoid problems, we don't use it
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
// Anyway shield is on over the LoLin32 board, so we won't be able to see this LED
#define HAS_RGB_LED 13 // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED
#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_13) // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED
#define HAS_BUTTON 15 // ESP32 GPIO15 (pin15) Button is on the LoraNode32 shield
#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown

View File

@ -12,7 +12,7 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#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_RGB_LED (0) // WS2812B RGB LED on GPIO0
#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0 (P2)
// Note: Pins for LORA chip SPI interface come from board file pins_arduino.h

View File

@ -20,8 +20,8 @@
//#define SPI_CS GPIO_NUM_36
#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_RGB_LED (0) // WS2812B RGB LED on GPIO0 (P2)
#define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED
#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0 (P2)
#define BOARD_HAS_PSRAM // use extra 4MB extern RAM
// Note: Pins for LORA chip SPI interface come from board file pins_arduino.h

View File

@ -23,7 +23,7 @@
#define HAS_LED 13 // ESP32 GPIO12 (pin22) On Board LED
//#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 SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_13) // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED
//#define HAS_BUTTON 15 // ESP32 GPIO15 (pin15) Button is on the LoraNode32 shield
//#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown

24
src/hal/tinypico.h Normal file
View File

@ -0,0 +1,24 @@
// clang-format off
// upload_speed 921600
// board esp32dev
#ifndef _TINYPICO_H
#define _TINYPICO_H
#include <stdint.h>
// Hardware related definitions for crowdsupply tinypico board
#define HAS_LED NOT_A_PIN // Green LED on board
#define HAS_RGB_LED Apa102 rgb_led(1, GPIO_NUM_12, GPIO_NUM_2) // APA102 RGB LED on board
//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
#define BAT_VOLTAGE_DIVIDER 2.7625f // voltage divider 160k/442k on board
#define BOARD_HAS_PSRAM // use extra 4MB external RAM
#define LED_POWER_SW (13) // switches LED power
#define LED_POWER_ON 0 // switch on transistor for LED power
#define LED_POWER_OFF 1
#endif

46
src/hal/tinypicomatrix.h Normal file
View File

@ -0,0 +1,46 @@
// clang-format off
// upload_speed 921600
// board esp32dev
#ifndef _TINYPICO_H
#define _TINYPICO_H
#include <stdint.h>
// Hardware related definitions for crowdsupply tinypico board
// for operating a 96x16 shift register LED matrix display
#define HAS_LED NOT_A_PIN // Green LED on board
#define HAS_RGB_LED Apa102 rgb_led(1, GPIO_NUM_12, GPIO_NUM_2) // APA102 RGB LED on board
//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
#define BAT_VOLTAGE_DIVIDER 2.7625f // voltage divider 160k/442k on board
#define BOARD_HAS_PSRAM // use extra 4MB external RAM
#define LED_POWER_SW (13) // switches LED power
#define LED_POWER_ON 0 // switch on transistor for LED power
#define LED_POWER_OFF 1
// LED Matrix display settings
#define HAS_MATRIX_DISPLAY 1 // Uncomment to enable LED matrix display output
#define LED_MATRIX_WIDTH (32*2) // Width (cols) in pixels (LEDs) of your display, must be 32X
#define LED_MATRIX_HEIGHT (16*1) // Height (rows) in pixels (LEDs) of your display, must be 16X
// Explanation of pin signals see https://learn.adafruit.com/32x16-32x32-rgb-led-matrix/new-wiring
#define MATRIX_DISPLAY_SCAN_US 500 // Matrix display scan rate in microseconds (1ms is about 'acceptable')
#define LED_MATRIX_LATCHPIN 32 // LAT (or STB = Strobe)
#define LED_MATRIX_CLOCKPIN 33 // CLK
#define LED_MATRIX_EN_74138 21 // EN (or OE)
#define LED_MATRIX_LA_74138 23 // LA (or A)
#define LED_MATRIX_LB_74138 19 // LB (or B)
#define LED_MATRIX_LC_74138 18 // LC (or C)
#define LED_MATRIX_LD_74138 5 // LD (or D)
#define LED_MATRIX_DATA_R1 22 // R1 (or R0)
// CLK: The clock signal moves the data bits from pin R1 ("red") in the shift registers
// LAT: The latch signal enables LEDs according to the shift register's contents
// Line Selects: LA, LB, LC, LD select which rows of the display are currently lit (0 .. 15)
// OE: Output enable switches the LEDs on/off while transitioning from one row to the next
#endif

View File

@ -17,7 +17,6 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define BOARD_HAS_PSRAM // use extra 4MB external RAM
#define HAS_BUTTON GPIO_NUM_39 // on board button (next to reset)
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board
@ -29,9 +28,9 @@
// enable only if device has these sensors, otherwise comment these lines
// BME680 sensor on I2C bus
//#define HAS_BME 1 // Enable BME sensors in general
//#define HAS_BME680 SDA, SCL
//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !!
#define HAS_BME 1 // Enable BME sensors in general
#define HAS_BME680 SDA, SCL
#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !!
// display (if connected)
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C

74
src/hal/ttgobeam10.h Normal file
View File

@ -0,0 +1,74 @@
// clang-format off
// upload_speed 921600
// board ttgo-t-beam
#ifndef _TTGOBEAM_H
#define _TTGOBEAM_H
#include <stdint.h>
/*
Hardware related definitions for TTGO T-Beam board
(only) for newer T-Beam version T22_V10
pinouts taken from https://github.com/lewisxhe/TTGO-T-Beam
/// Button functions: ///
Power, short press -> set device on (toggles display while device is on)
Power, long press -> set device off
User, short press -> flip display page
User, long press -> send LORA message
Reset -> reset device
*/
//#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define MY_OLED_SDA SDA
#define MY_OLED_SCL SCL
#define MY_OLED_RST U8X8_PIN_NONE
//#define DISPLAY_FLIP 1 // use if display is rotated
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_BUTTON GPIO_NUM_38 // middle on board button
#define HAS_PMU 1 // AXP192 power management chip
#define PMU_INT GPIO_NUM_35 // AXP192 interrupt
#define HAS_LED NOT_A_PIN
// GPS settings
#define HAS_GPS 1 // use on board GPS
#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_34, GPIO_NUM_12 // UBlox NEO 6M
#define GPS_INT GPIO_NUM_37 // 30ns accurary timepulse generated by NEO 6M Pin #3
// enable only if device has these sensors, otherwise comment these lines
// BME680 sensor on I2C bus
//#define HAS_BME 1 // Enable BME sensors in general
//#define HAS_BME680 SDA, SCL
//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !!
// user defined sensors (if connected)
//#define HAS_SENSORS 1 // comment out if device has user defined sensors
//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#endif
/*
// T-Beam V10 has on board power management by AXP192 PMU chip:
//
// DCDC1 0.7-3.5V @ 1200mA -> OLED
// DCDC3 0.7-3.5V @ 700mA -> ESP32 (keep this on!)
// LDO1 30mA -> GPS Backup
// LDO2 200mA -> LORA
// LDO3 200mA -> GPS
// Wiring for I2C OLED display:
//
// Signal Header OLED
// 3V3 7 VCC
// GND 8 GND
// IO22(SCL) 9 SCL
// IO21(SDA) 10 SDA
//
*/

View File

@ -11,8 +11,8 @@
// LED Matrix display settings
#define HAS_MATRIX_DISPLAY 1 // Uncomment to enable LED matrix display output
#define LED_MATRIX_WIDTH 64 // Width in pixels (LEDs) of your display
#define LED_MATRIX_HEIGHT 16 // Height in pixels (LEDs ) of your display
#define LED_MATRIX_WIDTH (32*2) // Width (cols) in pixels (LEDs) of your display, must be 32X
#define LED_MATRIX_HEIGHT (16*1) // Height (rows) in pixels (LEDs) of your display, must be 16X
// Pin numbers work fine for Wemos Lolin32 board (all used pins are on 1 side of the board)
// Explanation of pin signals see https://learn.adafruit.com/32x16-32x32-rgb-led-matrix/new-wiring

66
src/i2cscan.cpp Normal file
View File

@ -0,0 +1,66 @@
// Basic config
#include "globals.h"
#include "i2cscan.h"
// Local logging tag
static const char TAG[] = __FILE__;
int i2c_scan(void) {
int i2c_ret, addr;
int devices = 0;
ESP_LOGI(TAG, "Starting I2C bus scan...");
// block i2c bus access
if (I2C_MUTEX_LOCK()) {
for (addr = 8; addr <= 119; addr++) {
// scan i2c bus with no more to 100KHz
Wire.beginTransmission(addr);
Wire.write(addr);
i2c_ret = Wire.endTransmission();
if (i2c_ret == 0) {
devices++;
switch (addr) {
case SSD1306_PRIMARY_ADDRESS:
case SSD1306_SECONDARY_ADDRESS:
ESP_LOGI(TAG, "0x%X: SSD1306 Display controller", addr);
break;
case BME_PRIMARY_ADDRESS:
case BME_SECONDARY_ADDRESS:
ESP_LOGI(TAG, "0x%X: Bosch BME MEMS", addr);
break;
case AXP192_PRIMARY_ADDRESS:
ESP_LOGI(TAG, "0x%X: AXP192 power management", addr);
break;
case QUECTEL_GPS_PRIMARY_ADDRESS:
ESP_LOGI(TAG, "0x%X: Quectel GPS", addr);
break;
case MCP_24AA02E64_PRIMARY_ADDRESS:
ESP_LOGI(TAG, "0x%X: 24AA02E64 serial EEPROM", addr);
break;
default:
ESP_LOGI(TAG, "0x%X: Unknown device", addr);
break;
}
} // switch
} // for loop
ESP_LOGI(TAG, "I2C scan done, %u devices found.", devices);
I2C_MUTEX_UNLOCK(); // release i2c bus access
} else
ESP_LOGE(TAG, "I2c bus busy - scan error");
return devices;
}

View File

@ -63,9 +63,15 @@ void irqHandler(void *pvParameters) {
}
#endif
// do we have a power event?
#if (HAS_PMU)
if (InterruptStatus & PMU_IRQ)
power_event_IRQ();
#endif
// is time to send the payload?
if (InterruptStatus & SENDCYCLE_IRQ)
sendCounter();
sendData();
}
}
@ -106,6 +112,18 @@ void IRAM_ATTR ButtonIRQ() {
}
#endif
#ifdef HAS_PMU
void IRAM_ATTR PMUIRQ() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(irqHandlerTask, PMU_IRQ, eSetBits,
&xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR();
}
#endif
void mask_user_IRQ() { xTaskNotify(irqHandlerTask, MASK_IRQ, eSetBits); }
void unmask_user_IRQ() { xTaskNotify(irqHandlerTask, UNMASK_IRQ, eSetBits); }

View File

@ -14,7 +14,7 @@ unsigned long LEDBlinkStarted = 0; // When (in millis() led blink started)
#ifdef HAS_RGB_LED
// RGB Led instance
SmartLed rgb_led(LED_WS2812, 1, HAS_RGB_LED);
HAS_RGB_LED;
float rgb_CalcColor(float p, float q, float t) {
if (t < 0.0f)

View File

@ -2,13 +2,14 @@
#include "globals.h"
#define NUMCHARS 5
#define MATRIX_DISPLAY_PAGES (2) // number of display pages
#define LINE_DIAGRAM_DIVIDER (2) // scales pax numbers to led rows
// local Tag for logging
static const char TAG[] = __FILE__;
uint8_t MatrixDisplayIsOn = 0;
static uint8_t displaybuf[LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT / 8] = {0};
static unsigned long ulLastNumMacs = 0;
static time_t ulLastTime = myTZ.toLocal(now());
@ -16,9 +17,6 @@ LEDMatrix matrix(LED_MATRIX_LA_74138, LED_MATRIX_LB_74138, LED_MATRIX_LC_74138,
LED_MATRIX_LD_74138, LED_MATRIX_EN_74138, LED_MATRIX_DATA_R1,
LED_MATRIX_LATCHPIN, LED_MATRIX_CLOCKPIN);
// Display Buffer 128 = 64 * 16 / 8
uint8_t displaybuf[LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT / NUMCHARS];
// --- SELECT YOUR FONT HERE ---
const FONT_INFO *ActiveFontInfo = &digital7_18ptFontInfo;
// const FONT_INFO *ActiveFontInfo = &arialNarrow_17ptFontInfo;
@ -31,14 +29,21 @@ const FONT_CHAR_INFO *ActiveFontCharInfo = ActiveFontInfo->Descriptors;
void init_matrix_display(bool reverse) {
ESP_LOGI(TAG, "Initializing LED Matrix display");
matrix.begin(displaybuf, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT);
if (MatrixDisplayIsOn)
matrix.on();
else
matrix.off();
if (reverse)
matrix.reverse();
matrix.clear();
DrawNumber(String("0"));
matrix.drawPoint(0, LED_MATRIX_HEIGHT - 1, 1);
} // init_display
void refreshTheMatrixDisplay(bool nextPage) {
static uint8_t DisplayPage = 0;
static uint8_t DisplayPage = 0, col = 0, row = 0;
uint8_t level;
char buff[16];
// if Matrixdisplay is switched off we don't refresh it to relax cpu
@ -48,25 +53,60 @@ void refreshTheMatrixDisplay(bool nextPage) {
// set display on/off according to current device configuration
if (MatrixDisplayIsOn != cfg.screenon) {
MatrixDisplayIsOn = cfg.screenon;
if (MatrixDisplayIsOn)
matrix.on();
else
matrix.off();
}
if (nextPage) {
DisplayPage =
(DisplayPage >= MATRIX_DISPLAY_PAGES - 1) ? 0 : (DisplayPage + 1);
matrix.clear();
col = 0;
}
switch (DisplayPage % MATRIX_DISPLAY_PAGES) {
// page 0: pax
// page 1: time
// page 0: number of current pax OR footfall line diagram
// page 1: time of day
case 0:
if (ulLastNumMacs != macs.size()) {
ulLastNumMacs = macs.size();
matrix.clear();
DrawNumber(String(ulLastNumMacs));
if (cfg.countermode == 1)
{ // cumulative counter mode -> display total number of pax
if (ulLastNumMacs != macs.size()) {
ulLastNumMacs = macs.size();
matrix.clear();
DrawNumber(String(ulLastNumMacs));
}
}
else { // cyclic counter mode -> plot a line diagram
if (ulLastNumMacs != macs.size()) {
// next count cycle?
if (macs.size() == 0) {
// matrix full? then scroll left 1 dot, else increment column
if (col < (LED_MATRIX_WIDTH - 1))
col++;
else
ScrollLeft(displaybuf, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT);
} else
matrix.drawPoint(col, row, 0); // clear current dot
// scale and set new dot
ulLastNumMacs = macs.size();
level = ulLastNumMacs / LINE_DIAGRAM_DIVIDER;
row = level <= LED_MATRIX_HEIGHT
? LED_MATRIX_HEIGHT - 1 - level % LED_MATRIX_HEIGHT
: 0;
matrix.drawPoint(col, row, 1);
}
}
break;
@ -164,4 +204,18 @@ uint8_t GetCharWidth(char cChar) {
return CharDescriptor.width;
}
void ScrollLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows) {
uint32_t i, k, idx;
const uint32_t x = cols / 8;
for (k = 0; k < rows; k++) {
// scroll a line with x bytes one dot to the left
for (i = 0; i < x - 1; ++i) {
idx = i + k * x;
buf[idx] = (buf[idx] << 1) | ((buf[idx + 1] >> 7) & 1);
}
buf[idx + 1] <<= 1;
}
}
#endif // HAS_MATRIX_DISPLAY

View File

@ -2,40 +2,33 @@
// COUNTRY SETTINGS
// --> please check with you local regulations for ISM band frequency use!
//
// CFG_eu868 EU 863-870 MHz
// CFG_us915 US 902-928 MHz
// CFG_au921 Australia 915-928 MHz
// CFG_as923 Asia 923 MHz
// CFG_in866 India 865-867 MHz
#define CFG_eu868 1
//#define CFG_us915 1
//#define CFG_in866 1
//#define CFG_au921 1
//#define CFG_as923 1
//#define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP /* for as923-JP */
#define CFG_eu868 1 // Europe (high band)
//#define CFG_eu433 1 // Europe (low band)
//#define CFG_us915 1 // USA, Canada and South America
//#define CFG_in866 1 // India
//#define CFG_au921 1 // Australia
//#define CFG_as923 1 // Asia
//#define CFG_cn783 1 // China (high band)
//#define CFG_cn490 1 // China (low band)
//#define CFG_kr920 1 // Korea
// LMIC LORAWAN STACK SETTINGS
// --> adapt to your device only if necessary
//#define LMIC_USE_INTERRUPTS 1
// use interrupts only if LORA_IRQ and LORA_DIO are connected to interrupt
// capable GPIO pins on your board, if not disable interrupts
#define LMIC_USE_INTERRUPTS 1
//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
// LMIC requires ticks to be 15.5μs - 100 μs long
#define US_PER_OSTICK_EXPONENT 4
#define US_PER_OSTICK (1 << US_PER_OSTICK_EXPONENT)
#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK)
// time sync via LoRaWAN network, note: not supported by TTNv2
// #define LMIC_ENABLE_DeviceTimeReq 1
// 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) this value if you are
// limited on battery.
// limited on battery.
// ATTN: VALUES > 7 WILL CAUSE RECEPTION AND JOIN PROBLEMS WITH HIGH SF RATES
#define CLOCK_ERROR_PROCENTAGE 5
//#define CLOCK_ERROR_PROCENTAGE 7
// Set this to 1 to enable some basic debug output (using printf) about
// RF settings used during transmission and reception. Set to 2 to
@ -49,6 +42,11 @@
// current implementation only works on AVR, though.
//#define LMIC_PRINTF_TO Serial
// Change the SPI clock speed if you encounter errors
// communicating with the radio.
// The standard range is 125kHz-8MHz, but some boards can go faster.
//#define LMIC_SPI_FREQ 1E6
// Any runtime assertion failures are printed to this serial port (or
// any other Print object). If this is unset, any failures just silently
// halt execution.
@ -90,7 +88,7 @@
// implementation is optimized for speed on 32-bit processors using
// fairly big lookup tables, but it takes up big amounts of flash on the
// AVR architecture.
//#define USE_ORIGINAL_AES
#define USE_ORIGINAL_AES
//
// This selects the AES implementation written by Ideetroon for their
// own LoRaWAN library. It also uses lookup tables, but smaller
@ -98,4 +96,4 @@
// also about twice as slow as the original).
// #define USE_IDEETRON_AES
//
#define USE_MBEDTLS_AES
//#define USE_MBEDTLS_AES

View File

@ -18,9 +18,31 @@ static const char TAG[] = "lora";
#endif
#endif
osjob_t sendjob;
QueueHandle_t LoraSendQueue;
TaskHandle_t lmicTask = NULL;
TaskHandle_t lmicTask = NULL, lorasendTask = NULL;
// table of LORAWAN MAC messages sent by the network to the device
// format: opcode, cmdname (max 19 chars), #bytes params
// source: LoRaWAN 1.1 Specification (October 11, 2017)
static const mac_t MACdn_table[] = {
{0x01, "ResetConf", 1}, {0x02, "LinkCheckAns", 2},
{0x03, "LinkADRReq", 4}, {0x04, "DutyCycleReq", 1},
{0x05, "RXParamSetupReq", 4}, {0x06, "DevStatusReq", 0},
{0x07, "NewChannelReq", 5}, {0x08, "RxTimingSetupReq", 1},
{0x09, "TxParamSetupReq", 1}, {0x0A, "DlChannelReq", 4},
{0x0B, "RekeyConf", 1}, {0x0C, "ADRParamSetupReq", 1},
{0x0D, "DeviceTimeAns", 5}, {0x0E, "ForceRejoinReq", 2},
{0x0F, "RejoinParamSetupReq", 1}};
// table of LORAWAN MAC messages sent by the device to the network
static const mac_t MACup_table[] = {
{0x01, "ResetInd", 1}, {0x02, "LinkCheckReq", 0},
{0x03, "LinkADRAns", 1}, {0x04, "DutyCycleAns", 0},
{0x05, "RXParamSetupAns", 1}, {0x06, "DevStatusAns", 2},
{0x07, "NewChannelAns", 1}, {0x08, "RxTimingSetupAns", 0},
{0x09, "TxParamSetupAns", 0}, {0x0A, "DlChannelAns", 1},
{0x0B, "RekeyInd", 1}, {0x0C, "ADRParamSetupAns", 0},
{0x0D, "DeviceTimeReq", 0}, {0x0F, "RejoinParamSetupAns", 1}};
class MyHalConfig_t : public Arduino_LMIC::HalConfiguration_t {
@ -135,7 +157,7 @@ void get_hard_deveui(uint8_t *pdeveui) {
uint8_t i2c_ret;
// Init this just in case, no more to 100KHz
Wire.begin(MY_OLED_SDA, MY_OLED_SCL, 100000);
Wire.begin(SDA, SCL, 100000);
Wire.beginTransmission(MCP_24AA02E64_I2C_ADDRESS);
Wire.write(MCP_24AA02E64_MAC_ADDRESS);
i2c_ret = Wire.endTransmission();
@ -209,19 +231,17 @@ void onEvent(ev_t ev) {
case EV_JOINED:
strcpy_P(buff, PSTR("JOINED"));
sprintf(display_line6, " "); // clear previous lmic status
// set data rate adaptation according to saved setting
LMIC_setAdrMode(cfg.adrmode);
// set cyclic lmic link check to off if no ADR because is not supported by
// ttn (but enabled by lmic after join)
LMIC_setLinkCheckMode(cfg.adrmode);
// Set data rate and transmit power (note: txpower seems to be ignored by
// the library)
switch_lora(cfg.lorasf, cfg.txpower);
// kickoff first send job
os_setCallback(&sendjob, lora_send);
// show effective LoRa parameters after join
// set data rate and transmit power to defaults only if we have no ADR
if (!cfg.adrmode)
LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower);
// show current devaddr
ESP_LOGI(TAG, "DEVaddr=%08X", LMIC.devaddr);
ESP_LOGI(TAG, "Radio parameters %s / %s / %s",
getSfName(updr2rps(LMIC.datarate)),
getBwName(updr2rps(LMIC.datarate)),
getCrName(updr2rps(LMIC.datarate)));
break;
case EV_RFU1:
@ -237,46 +257,7 @@ void onEvent(ev_t ev) {
break;
case EV_TXCOMPLETE:
#if (TIME_SYNC_LORASERVER)
// if last packet sent was a timesync request, store TX timestamp
if (LMIC.pendTxPort == TIMEPORT)
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
#endif
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK")
: PSTR("TX COMPLETE"));
sprintf(display_line6, " "); // clear previous lmic status
if (LMIC.dataLen) { // did we receive payload data -> display info
ESP_LOGI(TAG, "Received %d bytes of payload, RSSI %d SNR %d",
LMIC.dataLen, LMIC.rssi, LMIC.snr / 4);
sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4);
if (LMIC.txrxFlags & TXRX_PORT) { // FPort -> use to switch
switch (LMIC.frame[LMIC.dataBeg - 1]) {
case RCMDPORT: // opcode -> call rcommand interpreter
rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
break;
default:
#if (TIME_SYNC_LORASERVER)
// timesync answer -> call timesync processor
if (LMIC.frame[LMIC.dataBeg - 1] == TIMEPORT) {
recv_timesync_ans(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
break;
}
#endif
// unknown port -> display info
ESP_LOGI(TAG, "Received data on unsupported port #%d",
LMIC.frame[LMIC.dataBeg - 1]);
break;
}
}
}
strcpy_P(buff, PSTR("TX COMPLETE"));
break;
case EV_LOST_TSYNC:
@ -306,13 +287,7 @@ void onEvent(ev_t ev) {
case EV_TXSTART:
if (!(LMIC.opmode & OP_JOINING)) {
#if (TIME_SYNC_LORASERVER)
// if last packet sent was a timesync request, store TX time
if (LMIC.pendTxPort == TIMEPORT)
strcpy_P(buff, PSTR("TX TIMESYNC"));
else
#endif
strcpy_P(buff, PSTR("TX START"));
strcpy_P(buff, PSTR("TX START"));
}
break;
@ -336,80 +311,58 @@ void onEvent(ev_t ev) {
// Log & Display if asked
if (*buff) {
ESP_LOGI(TAG, "%s", buff);
sprintf(display_line7, buff);
sprintf(lmic_event_msg, buff);
}
}
// helper function to assign LoRa datarates to numeric spreadfactor values
void switch_lora(uint8_t sf, uint8_t tx) {
if (tx > 20)
return;
cfg.txpower = tx;
switch (sf) {
case 7:
LMIC_setDrTxpow(DR_SF7, tx);
cfg.lorasf = sf;
break;
case 8:
LMIC_setDrTxpow(DR_SF8, tx);
cfg.lorasf = sf;
break;
case 9:
LMIC_setDrTxpow(DR_SF9, tx);
cfg.lorasf = sf;
break;
case 10:
LMIC_setDrTxpow(DR_SF10, tx);
cfg.lorasf = sf;
break;
case 11:
#if defined(CFG_us915)
LMIC_setDrTxpow(DR_SF11CR, tx);
cfg.lorasf = sf;
break;
#else
LMIC_setDrTxpow(DR_SF11, tx);
cfg.lorasf = sf;
break;
#endif
case 12:
#if defined(CFG_us915)
LMIC_setDrTxpow(DR_SF12CR, tx);
cfg.lorasf = sf;
break;
#else
LMIC_setDrTxpow(DR_SF12, tx);
cfg.lorasf = sf;
break;
#endif
default:
break;
}
}
// LMIC send task
void lora_send(void *pvParameters) {
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
void lora_send(osjob_t *job) {
MessageBuffer_t SendBuffer;
// Check if there is a pending TX/RX job running, if yes don't eat data
// since it cannot be sent right now
if ((LMIC.opmode & (OP_JOINING | OP_REJOIN | OP_TXDATA | OP_POLL)) != 0) {
// waiting for LoRa getting ready
} else {
if (xQueueReceive(LoraSendQueue, &SendBuffer, (TickType_t)0) == pdTRUE) {
// SendBuffer now filled with next payload from queue
if (!LMIC_setTxData2(SendBuffer.MessagePort, SendBuffer.Message,
SendBuffer.MessageSize, (cfg.countermode & 0x02))) {
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");
while (1) {
// postpone until we are joined if we are not
while (!LMIC.devaddr) {
vTaskDelay(pdMS_TO_TICKS(500));
}
// fetch next or wait for payload to send from queue
if (xQueueReceive(LoraSendQueue, &SendBuffer, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!");
continue;
}
// attempt to transmit payload
else {
switch (LMIC_sendWithCallback_strict(
SendBuffer.MessagePort, SendBuffer.Message, SendBuffer.MessageSize,
(cfg.countermode & 0x02), myTxCallback, NULL)) {
case LMIC_ERROR_SUCCESS:
ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize);
break;
case LMIC_ERROR_TX_BUSY: // LMIC already has a tx message pending
case LMIC_ERROR_TX_FAILED: // message was not sent
// ESP_LOGD(TAG, "LMIC busy, message re-enqueued"); // very noisy
vTaskDelay(pdMS_TO_TICKS(1000 + random(500))); // wait a while
lora_enqueuedata(&SendBuffer); // re-enqueue the undelivered message
break;
case LMIC_ERROR_TX_TOO_LARGE: // message size exceeds LMIC buffer size
case LMIC_ERROR_TX_NOT_FEASIBLE: // message too large for current datarate
ESP_LOGI(TAG,
"Message too large to send, message not sent and deleted");
// we need some kind of error handling here -> to be done
break;
default: // other LMIC return code
ESP_LOGE(TAG, "LMIC error, message not sent and deleted");
} // switch
}
delay(2); // yield to CPU
}
// reschedule job every 0,5 - 1 sec. including a bit of random to prevent
// systematic collisions
os_setTimedCallback(job, os_getTime() + 500 + ms2osticks(random(500)),
lora_send);
}
esp_err_t lora_stack_init() {
@ -422,13 +375,13 @@ esp_err_t lora_stack_init() {
ESP_LOGI(TAG, "LORA send queue created, size %d Bytes",
SEND_QUEUE_SIZE * sizeof(MessageBuffer_t));
// starting lorawan stack
// start lorawan stack
ESP_LOGI(TAG, "Starting LMIC...");
xTaskCreatePinnedToCore(lmictask, // task function
"lmictask", // name of task
4096, // stack size of task
(void *)1, // parameter of the task
2, // priority of the task
5, // priority of the task
&lmicTask, // task handle
1); // CPU core
@ -436,18 +389,31 @@ esp_err_t lora_stack_init() {
ESP_LOGI(TAG, "Already joined");
}
return ESP_OK; // continue main program
// start lmic send task
xTaskCreatePinnedToCore(lora_send, // task function
"lorasendtask", // name of task
3072, // stack size of task
(void *)1, // parameter of the task
1, // priority of the task
&lorasendTask, // task handle
1); // CPU core
return ESP_OK;
}
void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
void lora_enqueuedata(MessageBuffer_t *message) {
// enqueue message in LORA send queue
BaseType_t ret;
BaseType_t ret = pdFALSE;
MessageBuffer_t DummyBuffer;
sendprio_t prio = message->MessagePrio;
switch (prio) {
case prio_high:
// clear space in queue if full, then fallthrough to normal
if (uxQueueSpacesAvailable(LoraSendQueue) == 0)
// clear some space in queue if full, then fallthrough to prio_normal
if (uxQueueSpacesAvailable(LoraSendQueue) == 0) {
xQueueReceive(LoraSendQueue, &DummyBuffer, (TickType_t)0);
ESP_LOGW(TAG, "LORA sendqueue purged, data is lost");
}
case prio_normal:
ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0);
break;
@ -519,17 +485,17 @@ void lmictask(void *pvParameters) {
os_init(); // initialize lmic run-time environment
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)
// 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.
#ifdef CLOCK_ERROR_PROCENTAGE
LMIC_setClockError(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100);
#endif
//#if defined(CFG_US915) || defined(CFG_au921)
#if CFG_LMIC_US_like
// 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,
@ -537,10 +503,161 @@ void lmictask(void *pvParameters) {
LMIC_selectSubBand(1);
#endif
// 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);
// register a callback for downlink messages. We aren't trying to write
// reentrant code, so pUserData is NULL.
LMIC_registerRxMessageCb(myRxCallback, NULL);
while (1) {
os_runloop_once(); // execute lmic scheduled jobs and events
delay(2); // yield to CPU
}
} // lmictask
// receive message handler
void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
size_t nMsg) {
// display type of received data
if (nMsg)
ESP_LOGI(TAG, "Received %u byte(s) of payload on port %u", nMsg, port);
else if (port)
ESP_LOGI(TAG, "Received empty message on port %u", port);
// list MAC messages, if any
uint8_t nMac = pMsg - &LMIC.frame[0];
if (port != MACPORT)
--nMac;
if (nMac) {
ESP_LOGI(TAG, "%u byte(s) downlink MAC commands", nMac);
// NOT WORKING YET
// whe need to unwrap the MAC command from LMIC.frame here
// mac_decode(LMIC.frame, nMac, MACdn_table, sizeof(MACdn_table) /
// sizeof(MACdn_table[0]));
}
if (LMIC.pendMacLen) {
ESP_LOGI(TAG, "%u byte(s) uplink MAC commands", LMIC.pendMacLen);
mac_decode(LMIC.pendMacData, LMIC.pendMacLen, MACup_table,
sizeof(MACup_table) / sizeof(MACup_table[0]));
}
switch (port) {
// ignore mac messages
case MACPORT:
break;
// rcommand received -> call interpreter
case RCMDPORT:
rcommand(pMsg, nMsg);
break;
default:
#if (TIME_SYNC_LORASERVER)
// valid timesync answer -> call timesync processor
if ((port >= TIMEANSWERPORT_MIN) && (port <= TIMEANSWERPORT_MAX)) {
recv_timesync_ans(port, pMsg, nMsg);
break;
}
#endif
// unknown port -> display info
ESP_LOGI(TAG, "Received data on unsupported port %u", port);
break;
} // switch
}
// transmit complete message handler
void myTxCallback(void *pUserData, int fSuccess) {
#if (TIME_SYNC_LORASERVER)
// if last packet sent was a timesync request, store TX timestamp
if (LMIC.pendTxPort == TIMEPORT)
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
#endif
}
// decode LORAWAN MAC message
void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, const mac_t table[],
const uint8_t tablesize) {
if (!cmdlen)
return;
uint8_t foundcmd[cmdlen], cursor = 0;
while (cursor < cmdlen) {
int i = tablesize; // number of commands in table
while (i--) {
if (cmd[cursor] == table[i].opcode) { // lookup command in opcode table
cursor++; // strip 1 byte opcode
if ((cursor + table[i].params) <= cmdlen) {
memmove(foundcmd, cmd + cursor,
table[i].params); // strip opcode from cmd array
cursor += table[i].params;
ESP_LOGD(TAG, "MAC command %s", table[i].cmdname);
} else
ESP_LOGD(TAG, "MAC command 0x%02X with missing parameter(s)",
table[i].opcode);
break; // command found -> exit table lookup loop
} // end of command validation
} // end of command table lookup loop
if (i < 0) { // command not found -> skip it
ESP_LOGD(TAG, "Unknown MAC command 0x%02X", cmd[cursor]);
cursor++;
}
} // command parsing loop
} // mac_decode()
uint8_t getBattLevel() {
/*
return values:
MCMD_DEVS_EXT_POWER = 0x00, // external power supply
MCMD_DEVS_BATT_MIN = 0x01, // min battery value
MCMD_DEVS_BATT_MAX = 0xFE, // max battery value
MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level
*/
#if (defined HAS_PMU || defined BAT_MEASURE_ADC)
uint16_t voltage = read_voltage();
switch (voltage) {
case 0:
return MCMD_DEVS_BATT_NOINFO;
case 0xffff:
return MCMD_DEVS_EXT_POWER;
default:
return (voltage > OTA_MIN_BATT ? MCMD_DEVS_BATT_MAX : MCMD_DEVS_BATT_MIN);
}
#else // we don't have any info on battery level
return MCMD_DEVS_BATT_NOINFO;
#endif
} // getBattLevel()
// u1_t os_getBattLevel(void) { return getBattLevel(); };
const char *getSfName(rps_t rps) {
const char *const t[] = {"FSK", "SF7", "SF8", "SF9",
"SF10", "SF11", "SF12", "SF?"};
return t[getSf(rps)];
}
const char *getBwName(rps_t rps) {
const char *const t[] = {"BW125", "BW250", "BW500", "BW?"};
return t[getBw(rps)];
}
const char *getCrName(rps_t rps) {
const char *const t[] = {"CR 4/5", "CR 4/6", "CR 4/7", "CR 4/8"};
return t[getCr(rps)];
}
#endif // HAS_LORA

View File

@ -31,12 +31,12 @@ ledloop 0 3 blinks LEDs
spiloop 0 2 reads/writes data on spi interface
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
lmictask 1 5 MCCI LMiC LORAWAN stack
clockloop 1 4 generates realtime telegrams for external clock
timesync_req 1 3 processes realtime time sync requests
lmictask 1 2 MCCI LMiC LORAWAN stack
irqhandler 1 1 display, timesync, gps, etc. triggered by timers
gpsloop 1 1 reads data from GPS via serial or i2c
looptask 1 1 arduino loop (unused)
lorasendtask 1 1 feed data from lora sendqueue to lmcic
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
Low priority numbers denote low priority tasks.
@ -58,6 +58,7 @@ fired by hardware
DisplayIRQ -> esp32 timer 0 -> irqHandlerTask (Core 1)
CLOCKIRQ -> esp32 timer 1 -> ClockTask (Core 1)
ButtonIRQ -> external gpio -> irqHandlerTask (Core 1)
PMUIRQ -> PMU chip gpio -> irqHandlerTask (Core 1)
fired by software (Ticker.h)
TIMESYNC_IRQ -> timeSync() -> irqHandlerTask (Core 1)
@ -75,9 +76,9 @@ triggers pps 1 sec impulse
// Basic Config
#include "main.h"
configData_t cfg; // struct holds current device configuration
char display_line6[16], display_line7[16]; // display buffers
uint8_t volatile channel = 0; // channel rotation counter
configData_t cfg; // struct holds current device configuration
char lmic_event_msg[16]; // display buffer for LMIC event message
uint8_t volatile channel = 0; // channel rotation counter
uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
batt_voltage = 0; // globals for display
@ -169,24 +170,45 @@ void setup() {
ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion());
#endif
// open i2c bus
#ifdef HAS_DISPLAY
Wire.begin(MY_OLED_SDA, MY_OLED_SCL, 100000);
#else
Wire.begin(SDA, SCL, 100000);
#endif
// setup power on boards with power management logic
#ifdef EXT_POWER_SW
pinMode(EXT_POWER_SW, OUTPUT);
digitalWrite(EXT_POWER_SW, EXT_POWER_ON);
strcat_P(features, " VEXT");
#endif
#ifdef HAS_PMU
AXP192_init();
strcat_P(features, " PMU");
#endif
// scan i2c bus for devices
i2c_scan();
#endif // verbose
// read (and initialize on first run) runtime settings from NVRAM
loadConfig(); // includes initialize if necessary
// initialize display
#ifdef HAS_DISPLAY
strcat_P(features, " OLED");
DisplayIsOn = cfg.screenon;
init_display(PRODUCTNAME, PROGVERSION); // note: blocking call
#endif
#ifdef BOARD_HAS_PSRAM
assert(psramFound());
ESP_LOGI(TAG, "PSRAM found and initialized");
strcat_P(features, " PSRAM");
#endif
// set external power mode
#ifdef EXT_POWER_SW
pinMode(EXT_POWER_SW, OUTPUT);
digitalWrite(EXT_POWER_SW, EXT_POWER_ON);
strcat_P(features, " VEXT");
#endif
#ifdef BAT_MEASURE_EN
pinMode(BAT_MEASURE_EN, OUTPUT);
#endif
@ -195,17 +217,25 @@ void setup() {
#if (HAS_LED != NOT_A_PIN)
pinMode(HAS_LED, OUTPUT);
strcat_P(features, " LED");
#ifdef LED_POWER_SW
pinMode(LED_POWER_SW, OUTPUT);
digitalWrite(LED_POWER_SW, LED_POWER_ON);
#endif
#ifdef HAS_TWO_LED
pinMode(HAS_TWO_LED, OUTPUT);
strcat_P(features, " LED1");
#endif
// use LED for power display if we have additional RGB LED, else for status
#ifdef HAS_RGB_LED
switch_LED(LED_ON);
strcat_P(features, " RGB");
rgb_set_color(COLOR_PINK);
#endif
#endif
#endif // HAS_LED
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
// start led loop
@ -227,7 +257,7 @@ void setup() {
#endif
// initialize battery status
#ifdef BAT_MEASURE_ADC
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
strcat_P(features, " BATT");
calibrate_voltage();
batt_voltage = read_voltage();
@ -297,13 +327,6 @@ void setup() {
strcat_P(features, " FILTER");
#endif
// initialize display
#ifdef HAS_DISPLAY
strcat_P(features, " OLED");
DisplayIsOn = cfg.screenon;
init_display(PRODUCTNAME, PROGVERSION); // note: blocking call
#endif
// initialize matrix display
#ifdef HAS_MATRIX_DISPLAY
strcat_P(features, " LED_MATRIX");
@ -419,12 +442,10 @@ void setup() {
#warning you did not specify a time source, time will not be synched
#endif
/*
// initialize gps time
#if (HAS_GPS)
fetch_gpsTime();
#endif
*/
#if (defined HAS_IF482 || defined HAS_DCF77)
ESP_LOGI(TAG, "Starting Clock Controller...");
@ -445,18 +466,11 @@ void setup() {
ESP_LOGI(TAG, "Features:%s", features);
macsniff_setup();
uart_setup();
} // setup()
void loop() {
} // setup()
while (1) {
#if (HAS_LORA)
os_runloop_once(); // execute lmic scheduled jobs and events
#else
delay(2); // yield to CPU
#endif
}
}
void loop() { vTaskDelete(NULL); }

View File

@ -1,3 +1,5 @@
// clang-format off
// ----- Paxcounter user config file ------
//
// --> adapt to your needs and use case <--
@ -46,7 +48,8 @@
#define MEM_LOW 2048 // [Bytes] low memory threshold triggering a send cycle
#define RETRANSMIT_RCMD 5 // [seconds] wait time before retransmitting rcommand results
#define PAYLOAD_BUFFER_SIZE 51 // maximum size of payload block per transmit
#define LORASFDEFAULT 9 // 7 ... 12 SF, according to LoRaWAN specs
#define LORADRDEFAULT 5 // 0 .. 15, LoRaWAN datarate, according to regional LoRaWAN specs [default = 5]
#define LORATXPOWDEFAULT 7 // 0 .. 255, LoRaWAN TX power in dBm [default = 14]
#define MAXLORARETRY 500 // maximum count of TX retries if LoRa busy
#define SEND_QUEUE_SIZE 10 // maximum number of messages in payload send queue [1 = no queue]
@ -72,7 +75,7 @@
#define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off
#define TIME_SYNC_COMPILEDATE 0 // set to 1 to use compile date to initialize RTC after power outage [default = 0]
#define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 0]
#define TIME_SYNC_LORASERVER 1 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
// settings for syncing time with timeserver applications
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging
@ -85,6 +88,7 @@
// Ports on which the device sends and listenes on LoRaWAN and SPI
#define COUNTERPORT 1 // counts
#define MACPORT 0 // network commands
#define RCMDPORT 2 // remote commands
#define STATUSPORT 2 // remote command results
#define CONFIGPORT 3 // config query results
@ -107,4 +111,4 @@
#define CAYENNE_ACTUATOR 10 // actuator commands
#define CAYENNE_DEVICECONFIG 11 // device period configuration
#define CAYENNE_SENSORREAD 13 // sensor period configuration
#define CAYENNE_SENSORENABLE 14 // sensor enable configuration
#define CAYENNE_SENSORENABLE 14 // sensor enable configuration

View File

@ -43,7 +43,7 @@ void PayloadConvert::addVoltage(uint16_t value) {
}
void PayloadConvert::addConfig(configData_t value) {
buffer[cursor++] = value.lorasf;
buffer[cursor++] = value.loradr;
buffer[cursor++] = value.txpower;
buffer[cursor++] = value.adrmode;
buffer[cursor++] = value.screensaver;
@ -164,7 +164,7 @@ void PayloadConvert::addAlarm(int8_t rssi, uint8_t msg) {
void PayloadConvert::addVoltage(uint16_t value) { writeUint16(value); }
void PayloadConvert::addConfig(configData_t value) {
writeUint8(value.lorasf);
writeUint8(value.loradr);
writeUint8(value.txpower);
writeUint16(value.rssilimit);
writeUint8(value.sendcycle);
@ -378,7 +378,7 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float celsius,
uint32_t mem, uint8_t reset1, uint8_t reset2) {
uint16_t temp = celsius * 10;
uint16_t volt = voltage / 10;
#ifdef BAT_MEASURE_ADC
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
#if (PAYLOAD_ENCODER == 3)
buffer[cursor++] = LPP_BATT_CHANNEL;
#endif

231
src/power.cpp Normal file
View File

@ -0,0 +1,231 @@
// Basic config
#include "globals.h"
#include "power.h"
// Local logging tag
static const char TAG[] = __FILE__;
#ifdef HAS_PMU
AXP20X_Class pmu;
void power_event_IRQ(void) {
if (!I2C_MUTEX_LOCK())
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
pmu.readIRQ();
// put your power event handler code here
if (pmu.isVbusOverVoltageIRQ())
ESP_LOGI(TAG, "USB voltage %.2fV too high.", pmu.getVbusVoltage() / 1000);
if (pmu.isVbusPlugInIRQ())
ESP_LOGI(TAG, "USB plugged, %.2fV @ %.0mA", pmu.getVbusVoltage() / 1000,
pmu.getVbusCurrent());
if (pmu.isVbusRemoveIRQ())
ESP_LOGI(TAG, "USB unplugged.");
if (pmu.isBattPlugInIRQ())
ESP_LOGI(TAG, "Battery is connected.");
if (pmu.isBattRemoveIRQ())
ESP_LOGI(TAG, "Battery was removed.");
if (pmu.isChargingIRQ())
ESP_LOGI(TAG, "Battery charging.");
if (pmu.isChargingDoneIRQ())
ESP_LOGI(TAG, "Battery charging done.");
if (pmu.isBattTempLowIRQ())
ESP_LOGI(TAG, "Battery high temperature.");
if (pmu.isBattTempHighIRQ())
ESP_LOGI(TAG, "Battery low temperature.");
// display on/off
if (pmu.isPEKShortPressIRQ()) {
cfg.screenon = !cfg.screenon;
}
// shutdown power
if (pmu.isPEKLongtPressIRQ()) {
AXP192_power(false); // switch off Lora, GPS, display
pmu.shutdown();
}
pmu.clearIRQ();
I2C_MUTEX_UNLOCK();
} // mutex
// refresh stored voltage value
read_voltage();
}
void AXP192_power(bool on) {
if (on) {
pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON); // Lora on T-Beam V1.0
pmu.setPowerOutPut(AXP192_LDO3, AXP202_ON); // Gps on T-Beam V1.0
pmu.setPowerOutPut(AXP192_DCDC1, AXP202_ON); // OLED on T-Beam v1.0
// pmu.setChgLEDMode(AXP20X_LED_LOW_LEVEL);
pmu.setChgLEDMode(AXP20X_LED_BLINK_1HZ);
} else {
pmu.setChgLEDMode(AXP20X_LED_OFF);
pmu.setPowerOutPut(AXP192_DCDC1, AXP202_OFF);
pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF);
pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF);
}
}
void AXP192_showstatus(void) {
if (!I2C_MUTEX_LOCK())
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
if (pmu.isBatteryConnect())
if (pmu.isChargeing())
ESP_LOGI(TAG, "Battery charging, %.2fV @ %.0fmAh",
pmu.getBattVoltage() / 1000, pmu.getBattChargeCurrent());
else
ESP_LOGI(TAG, "Battery not charging");
else
ESP_LOGI(TAG, "No Battery");
if (pmu.isVBUSPlug())
ESP_LOGI(TAG, "USB powered, %.0fmW",
pmu.getVbusVoltage() / 1000 * pmu.getVbusCurrent());
else
ESP_LOGI(TAG, "USB not present");
I2C_MUTEX_UNLOCK();
} // mutex
}
void AXP192_init(void) {
// block i2c bus access
if (I2C_MUTEX_LOCK()) {
if (pmu.begin(Wire, AXP192_PRIMARY_ADDRESS))
ESP_LOGI(TAG, "AXP192 PMU initialization failed");
else {
// configure AXP192
pmu.setDCDC1Voltage(3300); // for external OLED display
pmu.setTimeOutShutdown(false); // no automatic shutdown
pmu.setTSmode(AXP_TS_PIN_MODE_DISABLE); // TS pin mode off to save power
// switch ADCs on
pmu.adc1Enable(AXP202_BATT_VOL_ADC1, true);
pmu.adc1Enable(AXP202_BATT_CUR_ADC1, true);
pmu.adc1Enable(AXP202_VBUS_VOL_ADC1, true);
pmu.adc1Enable(AXP202_VBUS_CUR_ADC1, true);
// switch power rails on
AXP192_power(true);
// I2C access of AXP202X library currently is not mutexable
// so we better should disable AXP interrupts... ?
#ifdef PMU_INT
pinMode(PMU_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PMU_INT), PMUIRQ, FALLING);
pmu.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ |
AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ |
AXP202_CHARGING_FINISHED_IRQ,
1);
pmu.clearIRQ();
#endif // PMU_INT
ESP_LOGI(TAG, "AXP192 PMU initialized");
}
I2C_MUTEX_UNLOCK(); // release i2c bus access
} else
ESP_LOGE(TAG, "I2c bus busy - PMU initialization error");
}
#endif // HAS_PMU
#ifdef BAT_MEASURE_ADC
esp_adc_cal_characteristics_t *adc_characs =
(esp_adc_cal_characteristics_t *)calloc(
1, sizeof(esp_adc_cal_characteristics_t));
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
static const adc1_channel_t adc_channel = BAT_MEASURE_ADC;
#else // ADC2
static const adc2_channel_t adc_channel = BAT_MEASURE_ADC;
#endif
static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_unit_t unit = ADC_UNIT_1;
#endif // BAT_MEASURE_ADC
void calibrate_voltage(void) {
#ifdef BAT_MEASURE_ADC
// configure ADC
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12));
ESP_ERROR_CHECK(adc1_config_channel_atten(adc_channel, atten));
#else // ADC2
// ESP_ERROR_CHECK(adc2_config_width(ADC_WIDTH_BIT_12));
ESP_ERROR_CHECK(adc2_config_channel_atten(adc_channel, atten));
#endif
// calibrate ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(
unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_characs);
// show ADC characterization base
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
ESP_LOGI(TAG,
"ADC characterization based on Two Point values stored in eFuse");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
ESP_LOGI(TAG,
"ADC characterization based on reference voltage stored in eFuse");
} else {
ESP_LOGI(TAG, "ADC characterization based on default reference voltage");
}
#endif
}
bool batt_sufficient() {
#if (defined HAS_PMU || defined BAT_MEASURE_ADC)
uint16_t volts = read_voltage();
return ((volts < 1000) ||
(volts > OTA_MIN_BATT)); // no battery or battery sufficient
#else
return true;
#endif
}
uint16_t read_voltage() {
uint16_t voltage = 0;
#ifdef HAS_PMU
if (!I2C_MUTEX_LOCK())
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
voltage = pmu.isVBUSPlug() ? 0xffff : pmu.getBattVoltage();
I2C_MUTEX_UNLOCK();
}
#else
#ifdef BAT_MEASURE_ADC
// multisample ADC
uint32_t adc_reading = 0;
int adc_buf = 0;
for (int i = 0; i < NO_OF_SAMPLES; i++) {
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
adc_reading += adc1_get_raw(adc_channel);
#else // ADC2
ESP_ERROR_CHECK(adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf));
adc_reading += adc_buf;
#endif
}
adc_reading /= NO_OF_SAMPLES;
// Convert ADC reading to voltage in mV
voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_characs);
#endif // BAT_MEASURE_ADC
#ifdef BAT_VOLTAGE_DIVIDER
voltage *= BAT_VOLTAGE_DIVIDER;
#endif // BAT_VOLTAGE_DIVIDER
#endif // HAS_PMU
return voltage;
}

View File

@ -19,32 +19,25 @@ void do_reset() {
void set_reset(uint8_t val[]) {
switch (val[0]) {
case 0: // restart device
sprintf(display_line6, "Reset pending");
do_reset();
break;
case 1: // reset MAC counter
ESP_LOGI(TAG, "Remote command: reset MAC counter");
reset_counters(); // clear macs
get_salt(); // get new salt
sprintf(display_line6, "Reset counter");
break;
case 2: // reset device to factory settings
ESP_LOGI(TAG, "Remote command: reset device to factory settings");
sprintf(display_line6, "Factory reset");
eraseConfig();
break;
case 3: // reset send queues
ESP_LOGI(TAG, "Remote command: flush send queue");
sprintf(display_line6, "Queue reset");
flushQueues();
break;
case 9: // reset and ask for software update via Wifi OTA
ESP_LOGI(TAG, "Remote command: software update via Wifi");
#if (USE_OTA)
sprintf(display_line6, "Software update");
cfg.runmode = 1;
#else
sprintf(display_line6, "Software update not implemented");
#endif // USE_OTA
break;
@ -127,10 +120,34 @@ void set_gps(uint8_t val[]) {
if (val[0]) {
cfg.payloadmask |= (uint8_t)GPS_DATA; // set bit in mask
} else {
cfg.payloadmask &= ~(uint8_t)GPS_DATA; // clear bit in mask
cfg.payloadmask &= (uint8_t)~GPS_DATA; // clear bit in mask
}
}
void set_bme(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: set BME mode to %s", val[0] ? "on" : "off");
if (val[0]) {
cfg.payloadmask |= (uint8_t)MEMS_DATA; // set bit in mask
} else {
cfg.payloadmask &= (uint8_t)~MEMS_DATA; // clear bit in mask
}
}
void set_batt(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: set battery mode to %s",
val[0] ? "on" : "off");
if (val[0]) {
cfg.payloadmask |= (uint8_t)BATT_DATA; // set bit in mask
} else {
cfg.payloadmask &= (uint8_t)~BATT_DATA; // clear bit in mask
}
}
void set_payloadmask(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: set payload mask to %X", val[0]);
cfg.payloadmask = val[0];
}
void set_sensor(uint8_t val[]) {
#if (HAS_SENSORS)
switch (val[0]) { // check if valid sensor number 1...4
@ -169,10 +186,22 @@ void set_monitor(uint8_t val[]) {
cfg.monitormode = val[0] ? 1 : 0;
}
void set_lorasf(uint8_t val[]) {
void set_loradr(uint8_t val[]) {
#if (HAS_LORA)
ESP_LOGI(TAG, "Remote command: set LoRa SF to %d", val[0]);
switch_lora(val[0], cfg.txpower);
if (validDR(val[0])) {
cfg.loradr = val[0];
ESP_LOGI(TAG, "Remote command: set LoRa Datarate to %d", cfg.loradr);
LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower);
ESP_LOGI(TAG, "Radio parameters now %s / %s / %s",
getSfName(updr2rps(LMIC.datarate)),
getBwName(updr2rps(LMIC.datarate)),
getCrName(updr2rps(LMIC.datarate)));
} else
ESP_LOGI(
TAG,
"Remote command: set LoRa Datarate called with illegal datarate %d",
val[0]);
#else
ESP_LOGW(TAG, "Remote command: LoRa not implemented");
#endif // HAS_LORA
@ -223,8 +252,16 @@ void set_rgblum(uint8_t val[]) {
void set_lorapower(uint8_t val[]) {
#if (HAS_LORA)
ESP_LOGI(TAG, "Remote command: set LoRa TXPOWER to %d", val[0]);
switch_lora(cfg.lorasf, val[0]);
// set data rate and transmit power only if we have no ADR
if (!cfg.adrmode) {
cfg.txpower = val[0];
ESP_LOGI(TAG, "Remote command: set LoRa TXPOWER to %d", cfg.txpower);
LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower);
} else
ESP_LOGI(
TAG,
"Remote command: set LoRa TXPOWER, not executed because ADR is on");
#else
ESP_LOGW(TAG, "Remote command: LoRa not implemented");
#endif // HAS_LORA
@ -239,14 +276,10 @@ void get_config(uint8_t val[]) {
void get_status(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: get device status");
#ifdef BAT_MEASURE_ADC
uint16_t voltage = read_voltage();
#else
uint16_t voltage = 0;
#endif
payload.reset();
payload.addStatus(voltage, uptime() / 1000, temperatureRead(), getFreeRAM(),
rtc_get_reset_reason(0), rtc_get_reset_reason(1));
payload.addStatus(read_voltage(), uptime() / 1000, temperatureRead(),
getFreeRAM(), rtc_get_reset_reason(0),
rtc_get_reset_reason(1));
SendPayload(STATUSPORT, prio_high);
};
@ -274,6 +307,17 @@ void get_bme(uint8_t val[]) {
#endif
};
void get_batt(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: get battery voltage");
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
payload.reset();
payload.addVoltage(read_voltage());
SendPayload(BATTPORT, prio_normal);
#else
ESP_LOGW(TAG, "Battery voltage not supported");
#endif
};
void get_time(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: get time");
payload.reset();
@ -297,26 +341,28 @@ void set_flush(uint8_t val[]) {
// format: opcode, function, #bytes params,
// flag (true = do make settings persistent / false = don't)
//
cmd_t table[] = {
static cmd_t table[] = {
{0x01, set_rssi, 1, true}, {0x02, set_countmode, 1, true},
{0x03, set_gps, 1, true}, {0x04, set_display, 1, true},
{0x05, set_lorasf, 1, true}, {0x06, set_lorapower, 1, true},
{0x05, set_loradr, 1, true}, {0x06, set_lorapower, 1, true},
{0x07, set_loraadr, 1, true}, {0x08, set_screensaver, 1, true},
{0x09, set_reset, 1, true}, {0x0a, set_sendcycle, 1, true},
{0x0b, set_wifichancycle, 1, true}, {0x0c, set_blescantime, 1, true},
{0x0d, set_vendorfilter, 1, false}, {0x0e, set_blescan, 1, true},
{0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true},
{0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false},
{0x13, set_sensor, 2, true}, {0x80, get_config, 0, false},
{0x81, get_status, 0, false}, {0x84, get_gps, 0, false},
{0x13, set_sensor, 2, true}, {0x14, set_payloadmask, 1, true},
{0x15, set_bme, 1, true}, {0x16, set_batt, 1, true},
{0x80, get_config, 0, false}, {0x81, get_status, 0, false},
{0x83, get_batt, 0, false}, {0x84, get_gps, 0, false},
{0x85, get_bme, 0, false}, {0x86, get_time, 0, false},
{0x87, set_time, 0, false}, {0x99, set_flush, 0, false}};
const uint8_t cmdtablesize =
static const uint8_t cmdtablesize =
sizeof(table) / sizeof(table[0]); // number of commands in command table
// check and execute remote command
void rcommand(uint8_t cmd[], uint8_t cmdlength) {
void rcommand(const uint8_t cmd[], const uint8_t cmdlength) {
if (cmdlength == 0)
return;

View File

@ -10,9 +10,12 @@ void sendcycle() {
// put data to send in RTos Queues used for transmit over channels Lora and SPI
void SendPayload(uint8_t port, sendprio_t prio) {
MessageBuffer_t SendBuffer; // contains MessageSize, MessagePort, Message[]
MessageBuffer_t
SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[]
SendBuffer.MessageSize = payload.getSize();
SendBuffer.MessagePrio = prio;
switch (PAYLOAD_ENCODER) {
case 1: // plain -> no mapping
case 2: // packed -> no mapping
@ -38,20 +41,20 @@ void SendPayload(uint8_t port, sendprio_t prio) {
default:
SendBuffer.MessagePort = port;
}
memcpy(SendBuffer.Message, payload.getBuffer(), payload.getSize());
memcpy(SendBuffer.Message, payload.getBuffer(), SendBuffer.MessageSize);
// enqueue message in device's send queues
#if (HAS_LORA)
lora_enqueuedata(&SendBuffer, prio);
lora_enqueuedata(&SendBuffer);
#endif
#ifdef HAS_SPI
spi_enqueuedata(&SendBuffer, prio);
spi_enqueuedata(&SendBuffer);
#endif
} // SendPayload
// interrupt triggered function to prepare payload to send
void sendCounter() {
void sendData() {
uint8_t bitmask = cfg.payloadmask;
uint8_t mask = 1;
@ -115,7 +118,7 @@ void sendCounter() {
break;
#endif
#ifdef BAT_MEASURE_ADC
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
case BATT_DATA:
payload.reset();
payload.addVoltage(read_voltage());
@ -128,7 +131,7 @@ void sendCounter() {
mask <<= 1;
} // while (bitmask)
} // sendCounter()
} // sendData()
void flushQueues() {
#if (HAS_LORA)

View File

@ -56,7 +56,7 @@ void spi_slave_task(void *param) {
memset(txbuf, 0, sizeof(txbuf));
memset(rxbuf, 0, sizeof(rxbuf));
// wait until data to send arrivey
// fetch next or wait for payload to send from queue
if (xQueueReceive(SPISendQueue, &msg, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!");
continue;
@ -147,10 +147,12 @@ esp_err_t spi_init() {
return ret;
}
void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
void spi_enqueuedata(MessageBuffer_t *message) {
// enqueue message in SPI send queue
BaseType_t ret;
MessageBuffer_t DummyBuffer;
sendprio_t prio = message->MessagePrio;
switch (prio) {
case prio_high:
// clear space in queue if full, then fallthrough to normal

View File

@ -54,12 +54,64 @@ void calibrateTime(void) {
}
#endif
goto finish;
finish:
setMyTime(t, t_msec, timeSource); // set time
setMyTime((uint32_t)t, t_msec, timeSource); // set time
} // calibrateTime()
// adjust system time, calibrate RTC and RTC_INT pps
void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
timesource_t mytimesource) {
// called with invalid timesource?
if (mytimesource == _unsynced)
return;
// increment t_sec only if t_msec > 1000
time_t time_to_set = (time_t)(t_sec + t_msec / 1000);
// do we have a valid time?
if (timeIsValid(time_to_set)) {
// if we have msec fraction, then wait until top of second with
// millisecond precision
if (t_msec % 1000) {
time_to_set++;
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000));
}
ESP_LOGD(TAG, "[%0.3f] UTC epoch time: %d.%03d sec", millis() / 1000.0,
time_to_set, t_msec % 1000);
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
// to top of second
#ifdef HAS_RTC
if ((mytimesource == _gps) || (mytimesource == _lora))
set_rtctime(time_to_set);
#endif
// if we have a software pps timer, shift it to top of second
#if (!defined GPS_INT && !defined RTC_INT)
timerWrite(ppsIRQ, 0); // reset pps timer
CLOCKIRQ(); // fire clock pps, this advances time 1 sec
#endif
setTime(time_to_set); // set the time on top of second
timeSource = mytimesource; // set global variable
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was set | source: %c",
millis() / 1000.0, timeSetSymbols[timeSource]);
} else {
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync);
ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c",
millis() / 1000.0, timeSetSymbols[timeSource]);
}
}
// helper function to setup a pulse per second for time synchronisation
uint8_t timepulse_init() {

View File

@ -108,7 +108,7 @@ void process_timesync_req(void *taskparameter) {
payload.addByte(0x99);
SendPayload(RCMDPORT, prio_high);
// ...send a alive open a receive window for last time_sync_answer
// LMIC_sendAlive();
LMIC_sendAlive();
}
} // end of for loop to collect timestamp samples
@ -181,7 +181,7 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
// the 5th byte contains the fractional seconds in 2^-8 second steps
// (= 1/250th sec), we convert this to ms
uint16_t timestamp_msec = 4 * buf[4];
// pointers to 4 bytes 4 bytes containing UTC seconds since unix epoch, msb
// pointers to 4 bytes containing UTC seconds since unix epoch, msb
uint32_t timestamp_sec, *timestamp_ptr;
// convert buffer to uint32_t, octet order is big endian
@ -210,92 +210,6 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
}
}
// adjust system time, calibrate RTC and RTC_INT pps
void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
timesource_t mytimesource) {
t_sec ++;
time_t time_to_set = (time_t)(t_sec);
// increment t_sec only if t_msec > 1000
time_to_set = (time_t)(t_sec + t_msec / 1000);
// do we have a valid time?
if (timeIsValid(time_to_set)) {
// if we have msec fraction, then wait until top of second with
// millisecond precision
if (t_msec % 1000) {
time_to_set++;
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000));
}
ESP_LOGD(TAG, "[%0.3f] UTC epoch time: %d.%03d sec", millis() / 1000.0,
time_to_set, t_msec % 1000);
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
// to top of second
#ifdef HAS_RTC
if ((mytimesource == _gps) || (mytimesource == _lora))
set_rtctime(time_to_set);
#endif
// if we have a software pps timer, shift it to top of second
#if (!defined GPS_INT && !defined RTC_INT)
timerWrite(ppsIRQ, 0); // reset pps timer
CLOCKIRQ(); // fire clock pps, this advances time 1 sec
#endif
struct timeval tv;
struct timezone tz;
if(gettimeofday(&tv, &tz) != 0) {
ESP_LOGE(TAG, "ERROR gettimeofday");
}
struct timeval beforeTime = tv;
struct timeval nowTime;
nowTime.tv_sec = t_sec;
nowTime.tv_usec = t_msec;
if(settimeofday(&nowTime, &tz) != 0) {
ESP_LOGE(TAG, "ERROR settimeofday");
}
struct timeval diff;
diff.tv_sec = nowTime.tv_sec-beforeTime.tv_sec;
diff.tv_usec = nowTime.tv_usec-beforeTime.tv_usec;
// sum up diff_s and diff_ms to one ms value
int32_t diff_s = diff.tv_sec;
int32_t diff_ms = diff.tv_usec/1000;
int32_t diff_ms_remain = diff_ms / 1000;
diff_s += diff_ms_remain;
diff_ms += -1000*diff_ms_remain;
if(diff_ms < 0) {
diff_s --;
diff_ms += 1000;
}
// cap diff at 24h (= 86,400s)
diff_s = diff_s % 86400;
int32_t timediff_ms = diff_s * 1000 + diff_ms;
// send diffTime
payload.reset();
payload.addTimeDiff(timediff_ms);
SendPayload(TIMEDIFFPORT, prio_high);
ESP_LOGI(TAG, "timediff_ms: %d", timediff_ms);
timeSource = mytimesource; // set global variable
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
time_uart_send_start();
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was set | source: %c",
millis() / 1000.0, timeSetSymbols[timeSource]);
} else {
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync);
ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c",
millis() / 1000.0, timeSetSymbols[timeSource]);
}
}
// create task for timeserver handshake processing, called from main.cpp
void timesync_init() {
xTaskCreatePinnedToCore(process_timesync_req, // task function

View File

@ -57,7 +57,7 @@ void wifi_sniffer_init(void) {
wificfg.nvs_enable = 0; // we don't need any wifi settings from NVRAM
wificfg.wifi_task_core_id = 0; // we want wifi task running on core 0
//wifi_promiscuous_filter_t filter = {
// wifi_promiscuous_filter_t filter = {
// .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT}; // only MGMT frames
// .filter_mask = WIFI_PROMIS_FILTER_MASK_ALL}; // we use all frames
@ -71,9 +71,9 @@ void wifi_sniffer_init(void) {
ESP_ERROR_CHECK(
esp_wifi_set_storage(WIFI_STORAGE_RAM)); // we don't need NVRAM
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL));
ESP_ERROR_CHECK(esp_wifi_stop());
ESP_ERROR_CHECK(
esp_wifi_set_promiscuous_filter(&filter)); // set frame filter
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); // no modem power saving
ESP_ERROR_CHECK(esp_wifi_start()); // must be started to be able to switch ch
ESP_ERROR_CHECK(esp_wifi_set_promiscuous_filter(&filter)); // set frame filter
ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler));
ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); // now switch on monitor mode