Contents — 26 sections
Project Overview
A weather station engineered to survive a glacier or a mountain ridge — extreme cold, wind, ice and total isolation — and still report reliably for a full season.
The places we most need mountain weather data are the places hardest to put an instrument: a glacier surface, a high ridge, an avalanche start zone, a remote pass. These sites drive water supply, avalanche risk and climate research, yet they sit far above any power line or cell tower, in temperatures that flatten ordinary batteries, under winds that tear away anything not built to hold, and under snow and rime ice that bury or freeze the very sensors you came to read. A weather station that works fine in a backyard will be dead within a week up there. This project is about the engineering that lets a node survive — because in high-altitude telemetry, ruggedness and power are the hard problems, and the sensing is almost the easy part.
The node measures the mountain essentials — temperature, humidity and barometric pressure, wind speed and direction, and snow depth (an ultrasonic sensor looking down at the snow surface, which is what actually matters for hydrology and avalanche work) — but every one of those measurements is shaped by the environment. The temperature sensor must be shielded and, ideally, aspirated so sun on the housing does not fake a warm reading; the anemometer must shed rime ice or its bearings freeze; the snow sensor's ultrasonic pulse must be temperature-corrected because the speed of sound changes sharply across the huge temperature range of a mountain day. The design treats each sensor's failure mode in the cold as a first-class problem.
The two dominant engineering constraints are power and communication. Lithium batteries lose capacity in the cold and — critically — must not be charged below freezing, so the power system has to manage temperature, not just voltage, and the node must sip energy through long, dark, storm-bound periods. And with no cellular coverage, the node reports over long-range LoRa to a valley gateway or, where even that is impossible, over a satellite link (e.g. Iridium) that costs real power and money per message, forcing a discipline of infrequent, compact, prioritised reporting. Everything is logged locally so a week-long storm that severs the link loses nothing. The result is a station that does the unglamorous thing brilliantly: it stays alive and keeps reporting from a place that is actively trying to kill it, turning a blank spot on the weather map into a season of data.
What this project does
- Measures temperature, humidity, pressure, wind and snow depth in extreme conditions
- Shields/aspirates the temperature sensor and temperature-corrects the snow reading
- Manages battery temperature — never charging lithium below freezing
- Sips power through long, dark, storm-bound periods on solar + battery
- Reports over long-range LoRa, or satellite where there is no other link
- Logs locally so a multi-day link outage loses no data
- Prioritises and compacts messages when every satellite byte costs power and money
Real-World Applications
| Setting | How it is used |
|---|---|
| Glaciology and climate research | Surface energy-balance and mass-balance data from glaciers and ice fields where no permanent station exists. |
| Avalanche forecasting | Wind, temperature and snow-depth data from start zones and ridges that feed avalanche risk assessment. |
| Mountain hydrology / water supply | Snowpack and weather data that drive melt and runoff forecasts for downstream water and hydropower. |
| Remote alpine infrastructure | Weather awareness for high passes, huts, telescopes and communication sites in the mountains. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Cold-survival power design (temperature-aware charging, deep sleep)
- Rime-ice-tolerant, wind-resistant sensor mounting
- Aspirated/shielded temperature and temperature-corrected snow depth
- LoRa or satellite backhaul for total isolation
- Local logging for multi-day outages
- Prioritised, compact reporting to conserve energy and airtime
- Season-long unattended operation in a hostile environment
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Advanced |
| Estimated completion time | 16–24 hours |
| Indicative build cost | ₹18,000 – ₹35,000 (site-dependent) |
| Primary discipline | Environment |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Cold-weather battery and solar management (temperature-aware charging)
- Ruggedising and mounting sensors against wind and rime ice
- Aspirated temperature measurement and temperature-corrected ranging
- Low-power design and prioritised, compact telemetry
- LoRa and satellite (e.g. Iridium) backhaul with local logging
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 1 | ₹450 |
| BME280 pressure/humidity/temperature sensor Self-heating skews temperature by ~1 °C — read in forced mode, not continuous. | 300–1100 hPa ±1 hPa, 0–100 %RH ±3 %, −40 to +85 °C ±1 °C, 3.4 µA at 1 Hz | 1 | ₹420 |
| JSN-SR04T waterproof ultrasonic sensor The 25 cm blind zone matters — mount it above the maximum expected water level. | 25–450 cm, ±1 cm, IP67 sealed transducer, 45° beam | 1 | ₹450 |
| DS18B20 waterproof temperature probe Dozens can share one GPIO — you address them by ROM code. | −55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID | 1 | ₹160 |
| SX1278 LoRa 433 MHz module (Ra-02) Never power the radio without an antenna — the PA will destroy itself. | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 1 | ₹480 |
| 20 W 12 V polycrystalline solar panel Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 1 | ₹1,200 |
| CN3791 MPPT solar charge controller Set the MPPT point to ~80 % of panel Voc for polycrystalline modules. | 4.5–28 V in, MPPT set by resistor divider, 2 A charge to a 1S Li-ion pack | 1 | ₹320 |
| 18650 Li-ion cell 3400 mAh + holder Never charge below 0 °C; always use a protected cell or a BMS. | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 1 | ₹450 |
| Anemometer + wind vane (rugged) Sonic anemometers avoid frozen bearings but cost more | Ice-shedding cup/sonic anemometer and vane rated for alpine wind | 1 | ₹3,500 |
| Cold-capable battery + heater/insulation The single hardest sub-system; get it right | LiFePO4 or cold-rated pack, insulated box, charge-gate/heater below 0 °C | 1 | ₹2,500 |
| Satellite modem (optional) Only where LoRa cannot reach; airtime is metered | Iridium SBD modem for sites with no LoRa path to a gateway | 1 | ₹12,000 |
| Aspirated radiation shield | Solar radiation shield with a low-power aspiration fan for true air temperature | 1 | ₹1,500 |
| Guyed mast + rugged enclosure | Wind-rated mast, guy wires, sealed UV/cold enclosure, rime-shedding surfaces | 1 | ₹3,000 |
Estimated total: ₹26,430, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 3.3 V logic / 5 V USB | UART, SPI, I²C, I²S, CAN, PWM | Datasheet |
| BME280 pressure/humidity/temperature sensor | 300–1100 hPa ±1 hPa, 0–100 %RH ±3 %, −40 to +85 °C ±1 °C, 3.4 µA at 1 Hz | 1.7–3.6 V (module has 3.3 V LDO) | I²C (0x76/0x77) or SPI | Datasheet |
| JSN-SR04T waterproof ultrasonic sensor | 25–450 cm, ±1 cm, IP67 sealed transducer, 45° beam | 5 V | Trigger/Echo or UART | Datasheet |
| DS18B20 waterproof temperature probe | −55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID | 3.0–5.5 V | 1-Wire (multi-drop) | Datasheet |
| SX1278 LoRa 433 MHz module (Ra-02) | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 3.3 V | SPI | Datasheet |
| 20 W 12 V polycrystalline solar panel | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 12 V nominal | MC4 / screw terminals | Datasheet |
| CN3791 MPPT solar charge controller | 4.5–28 V in, MPPT set by resistor divider, 2 A charge to a 1S Li-ion pack | 4.5–28 V | Solder pads | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0–4.2 V | Holder / spot-welded tabs | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. |
| BME280 pressure/humidity/temperature sensor | 1.7–3.6 V (module has 3.3 V LDO) | 0.4 | Self-heating skews temperature by ~1 °C — read in forced mode, not continuous. |
| JSN-SR04T waterproof ultrasonic sensor | 5 V | 30 | The 25 cm blind zone matters — mount it above the maximum expected water level. |
| DS18B20 waterproof temperature probe | 3.0–5.5 V | 1.5 | Dozens can share one GPIO — you address them by ROM code. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna — the PA will destroy itself. |
| 20 W 12 V polycrystalline solar panel | 12 V nominal | 1140 | Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. |
| CN3791 MPPT solar charge controller | 4.5–28 V | 2000 | Set the MPPT point to ~80 % of panel Voc for polycrystalline modules. |
Summed typical draw is 3451.9 mA. With a 1.5× design margin the supply should deliver at least 5200 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.
- Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
- Add
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonunder File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager. - Set the correct port under Tools → Port. On Linux add yourself to the
dialoutgroup:sudo usermod -aG dialout $USERand log out and back in. - Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
- Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| WiFi (ESP32 core) bundled | Station/AP connection management for the ESP32. | Bundled with the ESP32 Arduino core |
| Adafruit BME280 2.2.x | Compensation maths for the Bosch pressure/humidity/temperature sensor. | Library Manager → "Adafruit BME280 Library" |
| OneWire + DallasTemperature 2.3.x / 3.9.x | Bus enumeration and conversion commands for DS18B20 probes. | Library Manager → "DallasTemperature" (pulls OneWire) |
| Adafruit Unified Sensor 1.1.x | Common sensor event abstraction; a dependency of most Adafruit drivers. | Library Manager → "Adafruit Unified Sensor" |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager → "LoRa" by Sandeep Mistry |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager → "ArduinoJson" by Benoit Blanchon |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| BME280 | SDA/SCL | GPIO 21/22 | Air temp/RH/pressure (I²C, shielded) |
| Snow depth (ultrasonic) | TRIG/ECHO | GPIO 26/25 | Distance to snow surface |
| Anemometer | PULSE/UART | GPIO 27 / 16-17 | Wind speed (+ direction) |
| DS18B20 (battery) | DQ | GPIO 4 | Battery temperature (charge gate) |
| LoRa / Sat modem | SPI / UART | GPIO 18/19/23 / 16-17 | Backhaul |
| Aspiration fan | IN | GPIO 13 | Low-power fan for temp shield |
| Charge enable | EN | GPIO 12 | Gate charging on battery temp |
| MPPT + panel | OUT | Battery bus | Solar charging |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Sense battery temperature (DS18B20 on the pack) and gate charging via the charge-enable line — never let the MPPT charge the lithium pack below 0 °C.
- Mount the BME280 inside an aspirated radiation shield; run the low-power fan only when needed so its draw does not dominate the energy budget.
- Temperature-correct the snow-depth ultrasonic reading using air temperature — the speed of sound varies enough across a mountain day to matter to centimetres.
- Choose an ice-shedding or sonic anemometer and mount all moving parts to shed rime; frozen bearings are the classic alpine failure.
- Guy the mast for peak gusts and seal the enclosure against spindrift; route the satellite/LoRa antenna clear of the mast and ice build-up.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
At altitude the sensing is standard meteorology; the difficulty is that the environment corrupts or destroys naive measurements, so each sensor needs a survival strategy. Air temperature must come from an aspirated, shielded sensor: mountain sun is intense and an unshielded probe heats its own body several degrees above the true air temperature, and even a passive shield can read warm in still, sunny conditions, which is why a small fan drawing air past the sensor gives the honest reading energy budget permitting. Snow depth — often the whole point of the station — is measured by timing an ultrasonic echo off the snow surface, but the speed of sound falls with temperature, so the same echo time means a different distance at −25 °C than at +5 °C; without temperature correction the snow record drifts by centimetres over a day, which is the difference between useful and useless for hydrology. Wind is measured by an anemometer whose enemy is rime ice that locks the bearings; ice-shedding designs or sonic anemometers (no moving parts) are chosen precisely for that.
The power problem is the one that most often kills alpine stations, and it has a cruel twist: lithium-ion batteries not only lose capacity in the cold but must not be charged below 0 °C, because charging a frozen cell plates lithium metal and permanently damages (and can be a safety hazard) it. So the power system cannot just track voltage — it must sense the battery's temperature and gate charging, refusing solar charge when the pack is below freezing (or warming the pack, or using a cold-charge-tolerant chemistry). Combined with short winter days, frequent storm-obscured sun, and deep cold sapping capacity, this forces a design that sleeps almost all the time, wakes briefly to sample and log, and treats every milliwatt-hour as scarce. The station's longevity is decided here, not in the sensors.
Communication is the second dominant constraint and shapes the whole reporting philosophy. In a valley-visible site, long-range LoRa reaches a gateway cheaply, and the node can report fairly often. Where terrain blocks any LoRa path — deep in a range, on the far side of a ridge — the only option is a satellite link such as Iridium Short-Burst Data, and satellite airtime costs meaningful power and money per message and per byte. That inverts the usual telemetry mindset: instead of streaming, the node hoards data locally and transmits infrequently, compactly, and by priority — sending a hazardous change (a rapid pressure drop, a wind spike, a snow-loading event) promptly while batching routine observations into rare, dense packets. Local logging underpins all of it: a storm that severs the link for a week must not create a hole in the record, so the node always writes first and transmits when it can.
The unifying principle is that a high-altitude node is judged almost entirely on uptime in adversity. A backyard station is judged on accuracy; an alpine station is judged on whether it is still reporting after the first blizzard, the first −30 °C night, the first week without sun, the first rime event. So the engineering effort goes where the failures are: temperature-managed power, ice-and-wind-hardened mechanics, honest shielded sensing, and a frugal, resilient reporting discipline. Get those right and the ordinary weather sensors inside will deliver a season of data from a place that has never had any — which is exactly why these stations are worth the trouble.
The maths behind it
Temperature-corrected snow depth
Ultrasonic distance to the snow surface:
c(T) = 331.3 + 0.606·T_air (m/s)
distance = c(T) · t_echo / 2
snow_depth = sensor_height − distance
Across a mountain day T_air can swing 30 °C+, changing c by
~5% — several cm of apparent depth if uncorrected.
Cold-charge gate
Protect the lithium pack:
allow_charge = (T_batt > T_CHG_MIN) (T_CHG_MIN ~ 0–5 °C)
Below the threshold, inhibit the charger (or warm the pack).
Discharge is usually permitted colder than charge, but
capacity falls — budget for reduced usable Ah in the cold.
Energy budget in the dark
Survive the worst dark/storm run of D days:
usable_Wh ≥ D · daily_consumption
daily_consumption = wake_energy·N + comms_energy·M + sleep·24h
Satellite comms dominate M-term energy → keep M small.
Size battery for cold-derated capacity AND the longest
expected sunless period, not the average.
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Build the power system first
Assemble a cold-capable battery in an insulated box with a temperature sensor on the pack, charged through an MPPT controller whose charge path is gated by a charge-enable line the ESP32 controls.
Verify that below 0 °C the charger is inhibited, and size the pack for cold-derated capacity across your longest expected sunless period.
Mount and harden the sensors
Fit the temperature/humidity sensor in an aspirated radiation shield; mount an ice-shedding or sonic anemometer and vane; aim the snow-depth ultrasonic sensor straight down at the snow from a fixed, known height.
Guy the mast for peak gusts and ensure surfaces shed rime; seal the enclosure against spindrift.
Set up backhaul and logging
Fit LoRa for valley-visible sites or a satellite modem where terrain blocks it; mount the antenna clear of the mast and ice. Confirm local logging works before relying on any link.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Gate charging on battery temperature
Read the battery temperature every cycle and enable charging only above the safe threshold, inhibiting it (or warming the pack) when the battery is too cold.
cppcold-power.ino#define T_CHG_MIN 2.0f // never charge lithium below this (deg C) #define PIN_CHG_EN 12 // Called each wake before allowing any solar charge current. void manageCharging(float battTempC) { bool allow = battTempC > T_CHG_MIN; digitalWrite(PIN_CHG_EN, allow ? HIGH : LOW); // gate the MPPT charge path // Optionally: if cold but sun is available and a heater exists, // warm the pack toward T_CHG_MIN before enabling charge. }bool allow = battTempC > T_CHG_MINCharging is permitted only when the battery is above the safe threshold, protecting the cell from the permanent damage of cold-charging.digitalWrite(PIN_CHG_EN, allow ? HIGH : LOW)The ESP32 physically gates the charge path, so temperature — not just voltage — governs whether solar energy reaches the battery.// warm the pack toward T_CHG_MINWhere a small heater exists and sun is available, the pack can be warmed into the safe range so charging can resume, trading a little energy for battery health.Sample, correct and log
Read weather and snow depth, temperature-correct the snow reading, run the aspiration fan only as needed, and write every observation to local storage before considering transmission.
Report by priority within the energy/airtime budget
Send routine observations in infrequent, compact batches; promote a hazardous change (rapid pressure drop, wind spike, snow-loading) to an immediate compact message — but only if the power budget allows.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
/* ═══════════════════════════════════════════════════════════════
High-Altitude Telemetry Node — ESP32, hardened weather, LoRa/sat
Survives extreme cold, wind and isolation: temperature-gated
charging, aspirated/corrected sensing, deep-sleep power sipping,
local logging, and infrequent prioritised backhaul over LoRa or
satellite.
══════════════════════════════════════════════════════════════════ */
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LoRa.h>
#include <SPI.h>
#include <Preferences.h>
#include <math.h>
#define PIN_TRIG 26
#define PIN_ECHO 25
#define PIN_WIND 27
#define OW_BATT 4
#define PIN_CHG_EN 12
#define PIN_FAN 13
#define LORA_CS 5
#define LORA_RST 14
#define LORA_DIO0 2
#define SENSOR_HEIGHT_CM 300.0f // snow sensor height above ground
#define T_CHG_MIN 2.0f
#define WIND_K 2.4f // km/h per Hz — calibrate
#define ROUTINE_EVERY 6 // send routine batch every 6 wakes (e.g. hourly*6)
Adafruit_BME280 bme;
OneWire ow(OW_BATT); DallasTemperature battT(&ow);
Preferences prefs;
RTC_DATA_ATTR uint16_t wakeCount = 0;
RTC_DATA_ATTR float prevPressure = NAN;
volatile uint32_t windPulses = 0;
void IRAM_ATTR windISR(){ windPulses++; }
float snowDepthCm(float tAir) {
float c = (331.3f + 0.606f * tAir) / 10000.0f; // cm/us
digitalWrite(PIN_TRIG,LOW); delayMicroseconds(2);
digitalWrite(PIN_TRIG,HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG,LOW);
long us = pulseIn(PIN_ECHO,HIGH,40000);
if(!us) return NAN;
float dist = us * c / 2.0f;
return SENSOR_HEIGHT_CM - dist;
}
float windKmh() {
windPulses = 0;
attachInterrupt(PIN_WIND, windISR, FALLING);
delay(3000); // 3 s count window
detachInterrupt(PIN_WIND);
return (windPulses / 3.0f) * WIND_K;
}
void logLocal(float t,float rh,float p,float wind,float snow) { /* append */ }
void sendPacket(bool hazard, float t,float rh,float p,float wind,float snow) {
SPI.begin();
LoRa.setPins(LORA_CS, LORA_RST, LORA_DIO0);
LoRa.begin(433E6); LoRa.setSpreadingFactor(11); // long range
LoRa.beginPacket();
LoRa.printf("{\"n\":48,\"t\":%.1f,\"rh\":%.0f,\"p\":%.0f,"
"\"wind\":%.1f,\"snow\":%.0f,\"haz\":%d}",
t, rh, p, wind, snow, hazard?1:0);
LoRa.endPacket();
LoRa.sleep();
}
void setup() {
Serial.begin(115200);
pinMode(PIN_TRIG,OUTPUT); pinMode(PIN_ECHO,INPUT);
pinMode(PIN_WIND,INPUT_PULLUP);
pinMode(PIN_CHG_EN,OUTPUT); pinMode(PIN_FAN,OUTPUT);
Wire.begin(21,22); bme.begin(0x76);
battT.begin();
wakeCount++;
// ── manage cold charging FIRST ──
battT.requestTemperatures();
float tBatt = battT.getTempCByIndex(0);
digitalWrite(PIN_CHG_EN, tBatt > T_CHG_MIN ? HIGH : LOW);
// ── aspirate then read air temperature honestly ──
digitalWrite(PIN_FAN, HIGH); delay(20000); // fan 20 s (budget)
float tAir = bme.readTemperature();
float rh = bme.readHumidity();
float p = bme.readPressure()/100.0f; // hPa
digitalWrite(PIN_FAN, LOW);
float snow = snowDepthCm(tAir);
float wind = windKmh();
logLocal(tAir, rh, p, wind, snow); // always log first
// ── hazard detection promotes an immediate message ──
float dP = isnan(prevPressure)? 0 : p - prevPressure;
prevPressure = p;
bool hazard = (dP < -3.0f) || (wind > 80.0f); // fast drop or gale
bool routineSlot = (wakeCount % ROUTINE_EVERY) == 0;
if (hazard || routineSlot)
sendPacket(hazard, tAir, rh, p, wind, snow); // else just log + sleep
// ── deep sleep to sip power ──
esp_sleep_enable_timer_wakeup(600ULL * 1000000ULL); // 10 min base
esp_deep_sleep_start();
}
void loop() {} // deep sleep restarts setup()
Configuration & Calibration
Configuration steps
- Set the snow-sensor height, wind constant, and the routine reporting cadence (balance freshness against energy/airtime).
- Set T_CHG_MIN and, if used, heater behaviour for your battery chemistry.
- Choose LoRa (long spreading factor) or satellite backhaul and the hazard-promotion thresholds.
- Size the battery and panel for cold-derated capacity across your longest expected sunless period.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Snow depth
Verify the temperature-corrected distance against a physical measurement at a couple of temperatures; confirm the sensor height and correction.
Temperature shield
Compare the aspirated reading against a reference in strong sun and calm air; if it reads warm without the fan, the aspiration is doing its job.
Power in the cold
Test the charge-gate at sub-zero temperatures and measure real capacity cold, so the energy budget reflects reality, not datasheet room-temperature figures.
Network Architecture & Connectivity
Communication protocol
Routine observations batch into infrequent compact packets; hazardous changes promote to immediate messages. Over satellite, every byte costs power and money, so payloads are minimal and prioritised, and local logging backs the whole record.
| Topic / endpoint | Direction | Payload |
|---|---|---|
alt/node/48/obs | node → gateway | compact weather + snow observation |
alt/node/48/hazard | node → gateway | promoted rapid-change event |
alt/node/48/health | node → gateway | battery temp/charge, RSSI, backlog size |
Message contract between the device and the broker.
Cloud platform configuration
A store archives the season's data for forecasting and research, raises hazard alerts, and tracks each node's power and link health so a struggling station is noticed before it goes silent.
Dashboard setup
Weather and snow-depth trends per station, hazard markers, and a power/link-health panel (battery temperature, charge state, backlog).
Mobile app integration
Hazard alerts (rapid pressure drop, gale, snow-loading) and a warning if a node's battery or link health is failing.
Security considerations
- Sign observations so research/forecast data cannot be spoofed.
- Keep local logging authoritative so a lost or metered link never loses the record.
- Alert on node silence or falling battery health so a rescue/service visit can be planned before total failure.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Chill the battery below 0 °C | Charging is inhibited; discharge still works |
| Put the temperature sensor in strong sun | Aspirated reading stays near true air temp; unaspirated reads warm |
| Vary air temperature and range to a target | Corrected snow depth stays accurate across temperatures |
| Simulate a rapid pressure drop / gale | Node promotes an immediate hazard message |
| Sever the link for a simulated multi-day period | Local log continues; backlog forwards on reconnect |
| Run a long low-sun cold cycle | Node survives on budget; power sub-system holds up |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The dashboard shows the station's weather (temperature, humidity, pressure, wind) and snow depth over time, flags hazard-promoted messages, and shows battery temperature/charge state and link health.
{
"n": 48,
"t": -18.4,
"rh": 72,
"p": 631,
"wind": 46.2,
"snow": 184,
"haz": 0
}
A compact routine observation at −18 °C, low mountain pressure (631 hPa reflects the altitude), moderate wind and 184 cm of snow — the kind of data these sites have never before provided, delivered on a tight energy and airtime budget.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Deep-sleep almost all the time; the aspiration fan and any comms are the main awake-energy costs, so budget them explicitly.
- Minimise satellite messages — batch routine data, promote only genuine hazards.
- Size everything for cold-derated capacity and the longest sunless period, not average conditions.
- Log locally and forward backlogs rather than blocking on a metered or intermittent link.
- Replace every
delay()with amillis()comparison — blocking delays are the single most common cause of dropped readings. - Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
- Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
- Use
uint8_t/uint16_twhere the range allows; on an 8-bit AVR a 32-bit add costs four times as much. - Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
- Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
- For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.
Safety Precautions
- Never charge lithium below freezing — protect the pack and avoid the safety hazard of cold-charging.
- Install and service in the mountains only with proper alpine safety, avalanche awareness and never alone.
- Guy masts and secure enclosures for peak wind and ice loads so the station cannot become a hazard.
- Treat the station as an input to expert forecasting (e.g. avalanche), not an authority in itself.
- Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
- Never power an RF module without its antenna fitted — the reflected power destroys the output stage. Check your local licence-free band and duty-cycle limits before transmitting.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Service before and after the season: inspect for ice/wind damage, reseal enclosures, check guy tension.
- Re-verify the charge-gate and battery health each season; cold ages packs.
- Clear rime from sensors and antenna; confirm the snow sensor's line of sight and height.
- Check local logging and backlog forwarding, and clean the solar panel of snow/rime.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
- Wash the panel every few weeks in dusty conditions; a visible dust film costs 15–25 % of the harvest.
- Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add incoming/outgoing radiation and surface-temperature sensors for full energy-balance research.
- Add a small pack heater with a smart budget to enable cold-day charging.
- Combine LoRa and satellite adaptively — LoRa when a gateway is reachable, satellite as fallback.
- On-device detection of snow-loading/avalanche-relevant events for smarter hazard promotion.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add over-the-air firmware updates so you never have to physically reach a deployed node again.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.