Files
weatherstation-datacollector/main.cpp
olaf f55c1fe6f1 Pool sensor v2: VCC monitoring, database resilience, receiver improvements
- 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
2026-01-25 11:25:15 +00:00

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
}