- Added voltage monitoring table and storage pipeline - Extended pool payload to 17 bytes with VCC field (protocol v2) - Improved database connection pool resilience (reduced pool size, aggressive recycling, pool disposal on failure) - Added environment variable support for database configuration - Fixed receiver MQTT deprecation warning (CallbackAPIVersion.VERSION2) - Silenced excessive RSSI status logging in receiver - Added reset flag tracking and reporting - Updated Docker compose with DB config and log rotation limits
707 lines
20 KiB
C++
707 lines
20 KiB
C++
#include <Arduino.h>
|
|
#include <SPI.h>
|
|
#include <Wire.h>
|
|
#include <OneWire.h>
|
|
#include <DallasTemperature.h>
|
|
#include <Adafruit_BME280.h>
|
|
#include <avr/wdt.h>
|
|
#include <avr/sleep.h>
|
|
#include <avr/power.h>
|
|
#include <EEPROM.h>
|
|
#include <avr/io.h>
|
|
|
|
// Capture and persist reset cause as early as possible
|
|
// Store MCUSR in a noinit section so C runtime doesn't clear it
|
|
static uint8_t mcusr_mirror __attribute__((section(".noinit")));
|
|
|
|
void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));
|
|
void wdt_init(void) {
|
|
mcusr_mirror = MCUSR;
|
|
MCUSR = 0;
|
|
wdt_disable();
|
|
__asm__ __volatile__ ("ret");
|
|
}
|
|
|
|
static uint8_t lastResetFlagsGlobal = 0;
|
|
|
|
// Battery Optimization Settings
|
|
#define ENABLE_CHANGE_DETECTION 1 // Only transmit when values change
|
|
#define TEMP_CHANGE_THRESHOLD 0.1 // °C - transmit if temp changes by this amount (reduced from 0.2 for better noise filtering)
|
|
#define HUM_CHANGE_THRESHOLD 2.0 // % - transmit if humidity changes by this amount
|
|
#define PRES_CHANGE_THRESHOLD 1.0 // hPa - transmit if pressure changes by this amount
|
|
#define ALIVE_INTERVAL 10 // Force transmission every N cycles even if no change (10*30s = 5min)
|
|
#define SENSOR_INTERVAL 30 // Read sensors every N seconds (30s = good balance)
|
|
|
|
// CC1101 Register addresses
|
|
#define CC1101_IOCFG0 0x02
|
|
#define CC1101_PKTLEN 0x06
|
|
#define CC1101_PKTCTRL1 0x07
|
|
#define CC1101_SYNC1 0x04
|
|
#define CC1101_SYNC0 0x05
|
|
#define CC1101_PKTCTRL0 0x08
|
|
#define CC1101_CHANNR 0x0A
|
|
#define CC1101_FSCTRL1 0x0B
|
|
#define CC1101_FREQ2 0x0D
|
|
#define CC1101_FREQ1 0x0E
|
|
#define CC1101_FREQ0 0x0F
|
|
#define CC1101_MDMCFG4 0x10
|
|
#define CC1101_MDMCFG3 0x11
|
|
#define CC1101_MDMCFG2 0x12
|
|
#define CC1101_MDMCFG1 0x13
|
|
#define CC1101_MDMCFG0 0x14
|
|
#define CC1101_DEVIATN 0x15
|
|
#define CC1101_MCSM1 0x17
|
|
#define CC1101_MCSM0 0x18
|
|
#define CC1101_FOCCFG 0x19
|
|
#define CC1101_BSCFG 0x1A
|
|
#define CC1101_AGCCTRL2 0x1B
|
|
#define CC1101_AGCCTRL1 0x1C
|
|
#define CC1101_AGCCTRL0 0x1D
|
|
#define CC1101_FREND1 0x21
|
|
#define CC1101_FREND0 0x22
|
|
#define CC1101_FSCAL3 0x23
|
|
#define CC1101_FSCAL2 0x24
|
|
#define CC1101_FSCAL1 0x25
|
|
#define CC1101_FSCAL0 0x26
|
|
#define CC1101_TEST2 0x2C
|
|
#define CC1101_TEST1 0x2D
|
|
#define CC1101_TEST0 0x2E
|
|
#define CC1101_PATABLE 0x3E
|
|
#define CC1101_TXFIFO 0x3F
|
|
|
|
// Command strobes
|
|
#define CC1101_SRES 0x30
|
|
#define CC1101_SCAL 0x33
|
|
#define CC1101_STX 0x35
|
|
#define CC1101_SIDLE 0x36
|
|
#define CC1101_SFTX 0x3B
|
|
|
|
// Status registers
|
|
#define CC1101_PARTNUM 0x30
|
|
#define CC1101_VERSION 0x31
|
|
#define CC1101_RSSI 0x34
|
|
#define CC1101_MARCSTATE 0x35
|
|
|
|
// Pin definitions
|
|
#define CC1101_CS 10
|
|
#define LED_PIN 4
|
|
#define ONE_WIRE_BUS 3
|
|
#define PRES_OFFSET 41.0F
|
|
|
|
#define MYNODEID 1
|
|
|
|
// Change detection variables
|
|
float lastTxDsTempC = -999.0;
|
|
float lastTxBmeTempC = -999.0;
|
|
float lastTxBmeHum = -999.0;
|
|
float lastTxBmePres = -999.0;
|
|
uint8_t cyclesSinceLastTx = 0;
|
|
|
|
// Sensor interval tracking
|
|
uint16_t wakeCount = 0;
|
|
volatile uint8_t watchdog_wakeups = 0; // Counter for watchdog interrupts
|
|
|
|
// Watchdog ISR - wakes MCU from sleep
|
|
ISR(WDT_vect) {
|
|
watchdog_wakeups++;
|
|
// Note: do NOT disable watchdog here - it will be re-enabled before next sleep
|
|
}
|
|
|
|
void sleep_1s(void) {
|
|
// Ensure serial is flushed before sleep
|
|
Serial.flush();
|
|
|
|
// Disable UART to save power during sleep (~2-3mA)
|
|
power_usart0_disable();
|
|
|
|
// Configure watchdog for 1-second interrupt-based wake
|
|
cli(); // Disable interrupts during setup
|
|
wdt_reset();
|
|
WDTCSR |= (1 << WDCE) | (1 << WDE); // Enable watchdog configuration change
|
|
WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP1); // 1-second, interrupt only
|
|
sei(); // Re-enable interrupts
|
|
|
|
// Set sleep mode to Power Down (lowest power: ~5-10µA)
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
sleep_enable();
|
|
|
|
// Enter power-down sleep - wakes on watchdog interrupt
|
|
sleep_cpu();
|
|
|
|
// Execution resumes here after watchdog interrupt
|
|
sleep_disable();
|
|
wdt_disable(); // Stop watchdog after waking
|
|
|
|
// Re-enable UART after sleep
|
|
power_usart0_enable();
|
|
|
|
// Small delay to let UART stabilize
|
|
delayMicroseconds(100);
|
|
}
|
|
|
|
// Payload structure
|
|
struct Payload {
|
|
uint8_t magic1;
|
|
uint8_t magic2;
|
|
uint8_t version;
|
|
uint8_t nodeId;
|
|
uint16_t seq;
|
|
int16_t t_ds10;
|
|
int16_t t_bme10;
|
|
uint16_t hum10;
|
|
uint16_t pres1;
|
|
uint16_t vcc; // VCC in millivolts
|
|
uint8_t crc;
|
|
};
|
|
|
|
// Ensure payload size matches radio PKTLEN expectations (17 bytes)
|
|
static_assert(sizeof(Payload) == 17, "Payload size must be 17 bytes");
|
|
|
|
Payload p;
|
|
static uint16_t seq = 0;
|
|
|
|
OneWire oneWire(ONE_WIRE_BUS);
|
|
DallasTemperature ds18(&oneWire);
|
|
Adafruit_BME280 bme;
|
|
static bool bme_ok = false;
|
|
|
|
struct BmeSample {
|
|
float tempC;
|
|
float hum;
|
|
float presHpa;
|
|
};
|
|
|
|
uint8_t calcCrc(const uint8_t *data, uint8_t len) {
|
|
uint8_t c = 0;
|
|
for (uint8_t i = 0; i < len; i++) c ^= data[i];
|
|
return c;
|
|
}
|
|
|
|
// Read VCC voltage in millivolts using internal 1.1V reference
|
|
uint16_t readVcc() {
|
|
// Set ADC reference to internal 1.1V and measure VCC
|
|
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
|
|
delay(2); // Wait for Vref to settle
|
|
ADCSRA |= _BV(ADSC); // Start conversion
|
|
while (bit_is_set(ADCSRA, ADSC)); // Wait for completion
|
|
uint16_t result = ADC;
|
|
// Calculate VCC in millivolts: internal_ref * 1024 / ADC reading
|
|
// Calibrated: 1450512 gives 3300mV on this chip (internal ref ~1.42V)
|
|
uint16_t vcc = (1450512L / result);
|
|
|
|
// Disable ADC to save power (~200µA)
|
|
power_adc_disable();
|
|
|
|
return vcc;
|
|
}
|
|
|
|
// Check if sensor values have changed enough to warrant transmission
|
|
// NOTE: This function ONLY checks for change. Alive/heartbeat logic is handled in loop().
|
|
bool valuesChanged(float dsTempC, float bmeTempC, float bmeHum, float bmePres) {
|
|
// Note: Pressure changes do NOT trigger a transmission. Pressure is sent only
|
|
// when temperature/humidity trigger or during alive/heartbeat transmissions.
|
|
#if ENABLE_CHANGE_DETECTION
|
|
if (abs(dsTempC - lastTxDsTempC) >= TEMP_CHANGE_THRESHOLD) return true;
|
|
if (abs(bmeTempC - lastTxBmeTempC) >= TEMP_CHANGE_THRESHOLD) return true;
|
|
if (abs(bmeHum - lastTxBmeHum) >= HUM_CHANGE_THRESHOLD) return true;
|
|
return false; // No significant changes in temp/humidity
|
|
#else
|
|
return true; // Always transmit if change detection disabled
|
|
#endif
|
|
}
|
|
|
|
// CC1101 SPI functions
|
|
void cc1101_WriteReg(uint8_t addr, uint8_t value) {
|
|
digitalWrite(CC1101_CS, LOW);
|
|
delayMicroseconds(10);
|
|
SPI.transfer(addr);
|
|
SPI.transfer(value);
|
|
delayMicroseconds(10);
|
|
digitalWrite(CC1101_CS, HIGH);
|
|
}
|
|
|
|
uint8_t cc1101_ReadReg(uint8_t addr) {
|
|
digitalWrite(CC1101_CS, LOW);
|
|
delayMicroseconds(10);
|
|
SPI.transfer(addr | 0x80); // Read bit
|
|
uint8_t val = SPI.transfer(0);
|
|
delayMicroseconds(10);
|
|
digitalWrite(CC1101_CS, HIGH);
|
|
return val;
|
|
}
|
|
|
|
void cc1101_WriteStrobe(uint8_t strobe) {
|
|
digitalWrite(CC1101_CS, LOW);
|
|
delayMicroseconds(10);
|
|
SPI.transfer(strobe);
|
|
delayMicroseconds(10);
|
|
digitalWrite(CC1101_CS, HIGH);
|
|
}
|
|
|
|
uint8_t cc1101_ReadStatus(uint8_t addr) {
|
|
digitalWrite(CC1101_CS, LOW);
|
|
delayMicroseconds(10);
|
|
SPI.transfer(addr | 0xC0); // Burst read bit
|
|
uint8_t val = SPI.transfer(0);
|
|
delayMicroseconds(10);
|
|
digitalWrite(CC1101_CS, HIGH);
|
|
return val;
|
|
}
|
|
|
|
void cc1101_SetPower(uint8_t paValue) {
|
|
digitalWrite(CC1101_CS, LOW);
|
|
delayMicroseconds(10);
|
|
SPI.transfer(CC1101_PATABLE | 0x40); // Burst write to PATABLE
|
|
for (uint8_t i = 0; i < 8; i++) {
|
|
SPI.transfer(paValue);
|
|
}
|
|
delayMicroseconds(10);
|
|
digitalWrite(CC1101_CS, HIGH);
|
|
}
|
|
|
|
// Select TX power based on battery voltage to prevent brown-out
|
|
uint8_t getAdaptiveTxPower(uint16_t vccMv) {
|
|
if (vccMv >= 3000) return 0xC0; // Full power at 3.0V+
|
|
else if (vccMv >= 2800) return 0x60; // Medium power at 2.8-3.0V (lowered from 0x84)
|
|
else return 0x40; // Low power below 2.8V
|
|
}
|
|
|
|
void cc1101_WriteTxFifo(uint8_t *data, uint8_t len) {
|
|
digitalWrite(CC1101_CS, LOW);
|
|
delayMicroseconds(10);
|
|
SPI.transfer(CC1101_TXFIFO | 0x40); // Burst write
|
|
for (uint8_t i = 0; i < len; i++) {
|
|
SPI.transfer(data[i]);
|
|
}
|
|
delayMicroseconds(10);
|
|
digitalWrite(CC1101_CS, HIGH);
|
|
}
|
|
|
|
BmeSample readBmeOnce() {
|
|
if (!bme_ok) {
|
|
BmeSample s{};
|
|
s.tempC = NAN; s.hum = NAN; s.presHpa = NAN;
|
|
return s;
|
|
}
|
|
BmeSample s{};
|
|
|
|
// Force a single reading
|
|
bme.setSampling(Adafruit_BME280::MODE_FORCED,
|
|
Adafruit_BME280::SAMPLING_X2,
|
|
Adafruit_BME280::SAMPLING_X16,
|
|
Adafruit_BME280::SAMPLING_X1);
|
|
delay(10);
|
|
|
|
s.tempC = bme.readTemperature();
|
|
s.hum = bme.readHumidity();
|
|
s.presHpa = (bme.readPressure() / 100.0F) + PRES_OFFSET;
|
|
|
|
// Return to sleep to save power
|
|
bme.setSampling(Adafruit_BME280::MODE_SLEEP,
|
|
Adafruit_BME280::SAMPLING_X2,
|
|
Adafruit_BME280::SAMPLING_X16,
|
|
Adafruit_BME280::SAMPLING_X1);
|
|
|
|
return s;
|
|
}
|
|
|
|
// Attempt to recover a stuck I2C bus by toggling SCL until SDA is released
|
|
static void i2cBusRecover() {
|
|
pinMode(A4, INPUT_PULLUP); // SDA
|
|
pinMode(A5, INPUT_PULLUP); // SCL
|
|
delay(5);
|
|
if (digitalRead(A4) == LOW) {
|
|
pinMode(A5, OUTPUT);
|
|
for (uint8_t i = 0; i < 9 && digitalRead(A4) == LOW; i++) {
|
|
digitalWrite(A5, LOW);
|
|
delayMicroseconds(5);
|
|
digitalWrite(A5, HIGH);
|
|
delayMicroseconds(5);
|
|
}
|
|
}
|
|
}
|
|
|
|
float readDs18Median(uint8_t samples = 3) {
|
|
float buf[5]; // up to 5 samples
|
|
samples = (samples > 5) ? 5 : samples;
|
|
for (uint8_t i = 0; i < samples; i++) {
|
|
wdt_reset(); // Feed watchdog during slow sensor reads
|
|
ds18.requestTemperatures();
|
|
ds18.setWaitForConversion(true); // block until done
|
|
buf[i] = ds18.getTempCByIndex(0);
|
|
}
|
|
// simple insertion sort for small N
|
|
for (uint8_t i = 1; i < samples; i++) {
|
|
float v = buf[i];
|
|
uint8_t j = i;
|
|
while (j > 0 && buf[j - 1] > v) { buf[j] = buf[j - 1]; j--; }
|
|
buf[j] = v;
|
|
}
|
|
return buf[samples / 2];
|
|
}
|
|
|
|
float smoothEma(float raw, float alpha = 0.2f) {
|
|
static float ema = NAN;
|
|
if (isnan(ema)) ema = raw;
|
|
else ema = alpha * raw + (1.0f - alpha) * ema;
|
|
return ema;
|
|
}
|
|
|
|
bool waitForIdle(uint16_t timeoutMs) {
|
|
unsigned long start = millis();
|
|
while (millis() - start < timeoutMs) {
|
|
if ((cc1101_ReadStatus(CC1101_MARCSTATE) & 0x1F) == 0x01) return true; // 0x01 = IDLE
|
|
delay(1);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cc1101_InitFSK() {
|
|
Serial.println("Initializing CC1101 in GFSK mode (1.2 kBaud, 5 kHz dev, 58 kHz BW)...");
|
|
|
|
// Reset CC1101
|
|
cc1101_WriteStrobe(CC1101_SRES);
|
|
delay(10);
|
|
|
|
// Configure for GFSK modulation at 868.3 MHz, 1.2 kBaud, 5 kHz deviation
|
|
cc1101_WriteReg(CC1101_IOCFG0, 0x06);
|
|
cc1101_WriteReg(CC1101_PKTCTRL0, 0x00);
|
|
cc1101_WriteReg(CC1101_PKTCTRL1, 0x00);
|
|
cc1101_WriteReg(CC1101_PKTLEN, sizeof(Payload));
|
|
// Debug check: confirm PKTLEN register matches struct size
|
|
uint8_t pktlenReg = cc1101_ReadReg(CC1101_PKTLEN);
|
|
Serial.print("PKTLEN(struct/reg): ");
|
|
Serial.print(sizeof(Payload));
|
|
Serial.print("/");
|
|
Serial.println(pktlenReg);
|
|
cc1101_WriteReg(CC1101_SYNC1, 0xD3);
|
|
cc1101_WriteReg(CC1101_SYNC0, 0x91);
|
|
cc1101_WriteReg(CC1101_CHANNR, 0x00);
|
|
cc1101_WriteReg(CC1101_FSCTRL1, 0x06);
|
|
|
|
// Set frequency to 868.3 MHz
|
|
cc1101_WriteReg(CC1101_FREQ2, 0x21);
|
|
cc1101_WriteReg(CC1101_FREQ1, 0x65);
|
|
cc1101_WriteReg(CC1101_FREQ0, 0x6A);
|
|
|
|
// Modem configuration
|
|
cc1101_WriteReg(CC1101_MDMCFG4, 0xF8);
|
|
cc1101_WriteReg(CC1101_MDMCFG3, 0x83);
|
|
cc1101_WriteReg(CC1101_MDMCFG2, 0x12);
|
|
cc1101_WriteReg(CC1101_MDMCFG1, 0x22);
|
|
cc1101_WriteReg(CC1101_MDMCFG0, 0xF8);
|
|
cc1101_WriteReg(CC1101_DEVIATN, 0x15);
|
|
cc1101_WriteReg(CC1101_MCSM0, 0x18);
|
|
cc1101_WriteReg(CC1101_FOCCFG, 0x1D);
|
|
cc1101_WriteReg(CC1101_BSCFG, 0x1C);
|
|
cc1101_WriteReg(CC1101_MCSM1, 0x30);
|
|
|
|
// AGC control
|
|
cc1101_WriteReg(CC1101_AGCCTRL2, 0xC7);
|
|
cc1101_WriteReg(CC1101_AGCCTRL1, 0x00);
|
|
cc1101_WriteReg(CC1101_AGCCTRL0, 0xB0);
|
|
|
|
// Front-end configuration
|
|
cc1101_WriteReg(CC1101_FREND1, 0xB6);
|
|
cc1101_WriteReg(CC1101_FREND0, 0x17);
|
|
cc1101_WriteReg(CC1101_FSCAL3, 0xEA);
|
|
cc1101_WriteReg(CC1101_FSCAL2, 0x2A);
|
|
cc1101_WriteReg(CC1101_FSCAL1, 0x00);
|
|
cc1101_WriteReg(CC1101_FSCAL0, 0x1F);
|
|
|
|
cc1101_WriteReg(CC1101_TEST2, 0x88);
|
|
cc1101_WriteReg(CC1101_TEST1, 0x31);
|
|
cc1101_WriteReg(CC1101_TEST0, 0x09);
|
|
|
|
// Set PA table for maximum TX power (~10dBm)
|
|
cc1101_SetPower(0xC0);
|
|
|
|
// Calibrate frequency synthesizer
|
|
cc1101_WriteStrobe(CC1101_SIDLE);
|
|
delay(1);
|
|
cc1101_WriteStrobe(CC1101_SCAL);
|
|
delay(3);
|
|
for (uint16_t i = 0; i < 500; i++) {
|
|
uint8_t st = cc1101_ReadStatus(CC1101_MARCSTATE) & 0x1F;
|
|
if (st != 0x13 && st != 0x14) break;
|
|
delay(1);
|
|
}
|
|
|
|
Serial.println("CC1101 initialization complete");
|
|
}
|
|
|
|
void setup() {
|
|
// Disable watchdog immediately
|
|
wdt_reset();
|
|
wdt_disable();
|
|
wdt_reset();
|
|
|
|
// Power optimization - disable unused peripherals
|
|
power_twi_disable(); // Will re-enable for I2C
|
|
power_spi_disable(); // Will re-enable for SPI
|
|
ACSR |= (1 << ACD); // Disable analog comparator (~25µA saved)
|
|
|
|
// Set up LED pin FIRST
|
|
pinMode(LED_PIN, OUTPUT);
|
|
digitalWrite(LED_PIN, HIGH);
|
|
|
|
wdt_reset();
|
|
|
|
// Initialize serial WITHOUT any delay
|
|
Serial.begin(9600);
|
|
// NO DELAY - immediate operation
|
|
|
|
wdt_reset();
|
|
|
|
Serial.println("\n========== BOOT ==========");
|
|
wdt_reset();
|
|
Serial.flush();
|
|
wdt_reset();
|
|
|
|
// Re-enable I2C and SPI for sensor initialization
|
|
power_twi_enable();
|
|
power_spi_enable();
|
|
|
|
// Report and persist the last reset cause
|
|
uint8_t lastResetFlags = mcusr_mirror;
|
|
lastResetFlagsGlobal = lastResetFlags;
|
|
EEPROM.update(0, lastResetFlags);
|
|
Serial.print("Reset flags: 0x");
|
|
Serial.print(lastResetFlags, HEX);
|
|
Serial.print(" (");
|
|
if (lastResetFlags & (1<<WDRF)) Serial.print("WDT ");
|
|
if (lastResetFlags & (1<<BORF)) Serial.print("BOR ");
|
|
if (lastResetFlags & (1<<EXTRF)) Serial.print("EXT ");
|
|
if (lastResetFlags & (1<<PORF)) Serial.print("POR ");
|
|
Serial.println(")");
|
|
Serial.flush();
|
|
|
|
digitalWrite(LED_PIN, LOW);
|
|
delay(100);
|
|
digitalWrite(LED_PIN, HIGH);
|
|
|
|
// Initialize SPI
|
|
Serial.println("Init SPI...");
|
|
Serial.flush();
|
|
SPI.begin();
|
|
pinMode(CC1101_CS, OUTPUT);
|
|
digitalWrite(CC1101_CS, HIGH);
|
|
Serial.println("SPI OK");
|
|
Serial.flush();
|
|
|
|
// Initialize I2C with bus recovery to avoid hangs
|
|
Serial.println("Init I2C...");
|
|
Serial.flush();
|
|
digitalWrite(LED_PIN, LOW);
|
|
delay(100);
|
|
digitalWrite(LED_PIN, HIGH);
|
|
|
|
i2cBusRecover();
|
|
Wire.begin();
|
|
Serial.println("I2C OK");
|
|
Serial.flush();
|
|
|
|
// Initialize BME280
|
|
Serial.print("Init BME280...");
|
|
Serial.flush();
|
|
bme_ok = bme.begin(0x76);
|
|
if (!bme_ok) bme_ok = bme.begin(0x77);
|
|
Serial.println(bme_ok ? "OK" : "FAIL");
|
|
Serial.flush();
|
|
|
|
digitalWrite(LED_PIN, LOW);
|
|
delay(100);
|
|
digitalWrite(LED_PIN, HIGH);
|
|
|
|
// Initialize CC1101
|
|
Serial.println("Init CC1101...");
|
|
Serial.flush();
|
|
cc1101_InitFSK();
|
|
Serial.println("CC1101 OK");
|
|
Serial.flush();
|
|
|
|
digitalWrite(LED_PIN, LOW);
|
|
delay(100);
|
|
digitalWrite(LED_PIN, HIGH);
|
|
|
|
Serial.println("About to init DS18B20...");
|
|
Serial.flush();
|
|
wdt_reset();
|
|
|
|
// Initialize DS18B20 - now with watchdog protection
|
|
ds18.begin();
|
|
wdt_reset();
|
|
ds18.setResolution(12);
|
|
wdt_reset();
|
|
|
|
Serial.print("Found ");
|
|
Serial.print(ds18.getDeviceCount());
|
|
Serial.println(" DS18B20 device(s)");
|
|
Serial.flush();
|
|
wdt_reset();
|
|
|
|
// All initialized
|
|
Serial.println("========== SETUP COMPLETE ==========");
|
|
Serial.flush();
|
|
|
|
digitalWrite(LED_PIN, LOW);
|
|
delay(100);
|
|
digitalWrite(LED_PIN, HIGH);
|
|
delay(100);
|
|
digitalWrite(LED_PIN, LOW);
|
|
delay(100);
|
|
digitalWrite(LED_PIN, HIGH);
|
|
|
|
Serial.println("Ready to transmit");
|
|
}
|
|
|
|
void loop() {
|
|
wakeCount++;
|
|
|
|
// Only read sensors every SENSOR_INTERVAL seconds
|
|
if (wakeCount % SENSOR_INTERVAL != 0) {
|
|
Serial.print("."); // Heartbeat indicator
|
|
Serial.flush();
|
|
sleep_1s(); // Sleep for 1 second
|
|
return;
|
|
}
|
|
|
|
wdt_reset();
|
|
Serial.println(""); // Newline after dots
|
|
Serial.print("[Cycle ");
|
|
Serial.print(wakeCount);
|
|
Serial.println("] Reading sensors...");
|
|
wdt_reset();
|
|
|
|
// Re-enable ADC for VCC reading (will be disabled after read)
|
|
power_adc_enable();
|
|
|
|
// Read DS18B20 temperature with median + EMA filtering
|
|
float dsRaw = readDs18Median(3); // ~2.25s total for 3 samples at 12-bit
|
|
wdt_reset();
|
|
float dsTempC = smoothEma(dsRaw, 0.2f);
|
|
wdt_reset();
|
|
|
|
// Read BME280
|
|
BmeSample bmeSample = readBmeOnce();
|
|
wdt_reset();
|
|
|
|
// Read VCC voltage
|
|
uint16_t vccMv = readVcc();
|
|
wdt_reset();
|
|
|
|
// Fill payload
|
|
p.magic1 = 0x42;
|
|
p.magic2 = 0x99;
|
|
// Bump payload format to version 2 (includes VCC field)
|
|
p.version = (uint8_t)(2 | ((lastResetFlagsGlobal & 0x0F) << 4));
|
|
p.nodeId = MYNODEID;
|
|
p.seq = seq++;
|
|
p.t_ds10 = (int16_t)(dsTempC * 10.0);
|
|
p.t_bme10 = (int16_t)(bmeSample.tempC * 10.0);
|
|
p.hum10 = (uint16_t)(bmeSample.hum * 10.0);
|
|
p.pres1 = (uint16_t)(bmeSample.presHpa * 10.0);
|
|
p.vcc = vccMv;
|
|
|
|
p.crc = 0;
|
|
p.crc = calcCrc((uint8_t*)&p, sizeof(Payload) - 1);
|
|
|
|
wdt_reset();
|
|
|
|
// Check if transmission is needed
|
|
cyclesSinceLastTx++;
|
|
bool changed = valuesChanged(dsTempC, bmeSample.tempC, bmeSample.hum, bmeSample.presHpa);
|
|
bool alive = (lastTxDsTempC != -999.0 && cyclesSinceLastTx >= ALIVE_INTERVAL) || (lastTxDsTempC == -999.0);
|
|
bool shouldTransmit = changed || alive;
|
|
|
|
wdt_reset();
|
|
|
|
if (!shouldTransmit) {
|
|
Serial.print("No change (cycle ");
|
|
Serial.print(cyclesSinceLastTx);
|
|
Serial.println("), skipping TX");
|
|
wdt_reset();
|
|
|
|
// Flash LED briefly to show we're alive but not transmitting
|
|
digitalWrite(LED_PIN, HIGH);
|
|
delay(50);
|
|
digitalWrite(LED_PIN, LOW);
|
|
wdt_reset();
|
|
|
|
// Sleep for remaining time of this cycle
|
|
Serial.flush();
|
|
sleep_1s();
|
|
return; // Skip transmission
|
|
}
|
|
|
|
// Values changed or alive signal - proceed with transmission
|
|
if (changed) {
|
|
Serial.print("Values changed, transmitting seq=");
|
|
} else {
|
|
Serial.print("ALIVE signal, transmitting seq=");
|
|
}
|
|
wdt_reset();
|
|
Serial.println(seq);
|
|
wdt_reset();
|
|
|
|
// Update last transmitted values
|
|
lastTxDsTempC = dsTempC;
|
|
lastTxBmeTempC = bmeSample.tempC;
|
|
lastTxBmeHum = bmeSample.hum;
|
|
lastTxBmePres = bmeSample.presHpa;
|
|
cyclesSinceLastTx = 0;
|
|
|
|
wdt_reset();
|
|
|
|
// Set fixed TX power to 0xC0 (~10 dBm - maximum)
|
|
cc1101_SetPower(0xC0);
|
|
|
|
// Transmit
|
|
cc1101_WriteStrobe(CC1101_SIDLE);
|
|
delay(5);
|
|
wdt_reset();
|
|
|
|
// Flush TX FIFO
|
|
cc1101_WriteStrobe(CC1101_SFTX);
|
|
delay(2);
|
|
|
|
// Write data to TX FIFO
|
|
cc1101_WriteTxFifo((uint8_t*)&p, sizeof(Payload));
|
|
wdt_reset();
|
|
|
|
// Start transmission
|
|
cc1101_WriteStrobe(CC1101_STX);
|
|
|
|
// Wait until radio returns to IDLE or timeout
|
|
bool txDone = waitForIdle(150);
|
|
wdt_reset();
|
|
if (!txDone) {
|
|
Serial.print("[tx-timeout]");
|
|
} else {
|
|
Serial.print(".");
|
|
}
|
|
wdt_reset();
|
|
|
|
// Go back to idle
|
|
cc1101_WriteStrobe(CC1101_SIDLE);
|
|
delay(5);
|
|
|
|
// Long blink to indicate TX occurred
|
|
digitalWrite(LED_PIN, HIGH);
|
|
delay(300);
|
|
digitalWrite(LED_PIN, LOW);
|
|
delay(100);
|
|
wdt_reset();
|
|
|
|
Serial.println(" Done!");
|
|
Serial.print("DS: "); Serial.print(dsTempC, 1);
|
|
Serial.print(" BME: "); Serial.print(bmeSample.tempC, 1);
|
|
Serial.print(" H: "); Serial.print(bmeSample.hum, 1);
|
|
Serial.print(" P: "); Serial.print(bmeSample.presHpa, 1);
|
|
Serial.print(" VCC: "); Serial.print(vccMv); Serial.println("mV");
|
|
Serial.flush();
|
|
wdt_reset();
|
|
|
|
sleep_1s(); // Sleep for 1 second after transmission
|
|
}
|