Contents — 26 sections
Project Overview
Puts every streetlight on one network so a city can control brightness centrally, and — crucially — knows the instant a light fails instead of waiting for a resident to complain.
A city runs tens of thousands of streetlights, and its two biggest lighting headaches are control and faults. Control: adjusting schedules and brightness across a whole city means sending someone to every pole, so in practice nothing changes. Faults: when a light fails, the city usually has no idea until a resident phones in — so dark spots persist for days or weeks, and maintenance crews drive around inspecting working lights to find the broken ones. This project networks every streetlight into a central system that solves both: brightness and schedules are controlled centrally, and every light reports its own status so a failure is known the instant it happens.
Each light becomes a networked node that does two things beyond switching on and off. It accepts central control — brightness levels, dimming schedules, and group commands (a whole street, zone, or the whole city) pushed from a management system, so a city-wide policy change is a click, not a fleet of vans. And it reports its own state — whether it is on, its brightness, its power draw, and above all whether it has failed (a commanded-on light drawing no current is a dead lamp) — so the central system always knows the true condition of every light. Fault reporting turns maintenance from reactive and blind into proactive and targeted: the city knows which lights are out, where, before anyone complains.
Networked over a low-power wide-area technology (LoRa) or a mesh, this scales to a whole city's lights on a live map — controllable, self-reporting, and analysable (energy, fault rates, burn-hours). The savings are real: proactive fault repair, remote brightness/scheduling policy, and the analytics to manage the asset. It is honest that a city deployment integrates with the lighting infrastructure and CMS properly, that safety/minimum-lighting rules and fail-safe behaviour apply, and that fault detection depends on reliable current sensing. But as a central-control-plus-fault-reporting streetlight network, it turns a sprawling, unmanaged, complaint-driven lighting estate into a centrally-managed one that fixes its own dark spots before they become problems.
What this project does
- Networks every streetlight into a central management system
- Controls brightness and schedules centrally (per light, group or city-wide)
- Reports each light's status: on/off, brightness, power
- Detects and reports faults (failed lamp) with location, proactively
- Turns maintenance from complaint-driven to targeted
- Provides a live city-wide map and analytics (energy, faults, burn-hours)
- Applies safety/minimum-lighting rules and fails safe
Real-World Applications
| Setting | How it is used |
|---|---|
| Municipal lighting management | Central control and fault reporting across a city's streetlights. |
| Proactive maintenance | Knowing which lights are out, where, before residents complain. |
| Policy/energy management | City-wide brightness/schedule changes and energy analytics. |
| Campus / estate lighting | Centrally-managed, self-reporting lighting for large sites. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Central brightness/schedule control (group + city-wide)
- Per-light status/brightness/power reporting
- Proactive located fault detection
- Live city-wide lighting map
- Energy/fault/burn-hour analytics
- LoRa/mesh scalability
- Safety/minimum-level and fail-safe
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Intermediate |
| Estimated completion time | 12–18 hours |
| Indicative build cost | ₹2,200 – ₹4,000 per light (+ CMS/gateway) |
| Primary discipline | Smart City |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Networked light control and central management
- Group/city-wide command distribution
- Status and fault reporting (current sensing)
- LoRa/mesh scalability
- Safety/minimum-level and fail-safe
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 |
| ACS712 hall-effect current sensor (20 A) Zero-offset drifts with temperature — re-zero at boot with no load. | ±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation | 1 | ₹180 |
| LDR (GL5528 photoresistor) + 10 kΩ divider Cheap and rugged, but non-linear and unit-to-unit variation is large. | 10–20 kΩ at 10 lx, 1 MΩ dark, peak response 540 nm | 1 | ₹15 |
| 5 V single-channel opto-isolated relay module Cut the JD-VCC jumper and feed the coil separately for true isolation. | SPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input | 1 | ₹90 |
| 0.96″ SSD1306 OLED display Static images burn in — invert or scroll the screen periodically. | 128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C | 1 | ₹250 |
| 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 |
| 5 V 3 A regulated SMPS adapter Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A. | 100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection | 1 | ₹350 |
| Dimmable driver interface Match to the luminaire | 0-10V/PWM/DALI to the luminaire driver | 1 | ₹600 |
| Current sensor (fault) | Confirms the lamp draws current (fault detection) | 1 | ₹200 |
| LoRa/mesh module + gateway | Node radio and city gateway/CMS | 1 | ₹900 |
| Central management system Software/platform | CMS mapping/controlling all lights and faults | 1 | — |
Estimated total: ₹3,515, 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 |
| ACS712 hall-effect current sensor (20 A) | ±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation | 5 V | Analogue | Datasheet |
| LDR (GL5528 photoresistor) + 10 kΩ divider | 10–20 kΩ at 10 lx, 1 MΩ dark, peak response 540 nm | any (passive) | Analogue divider | Datasheet |
| 5 V single-channel opto-isolated relay module | SPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input | 5 V coil | Digital (active-low) | Datasheet |
| 0.96″ SSD1306 OLED display | 128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C | 3.3–5 V | I²C (0x3C) | 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 |
| 5 V 3 A regulated SMPS adapter | 100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection | 5 V | DC barrel / USB | 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. |
| ACS712 hall-effect current sensor (20 A) | 5 V | 10 | Zero-offset drifts with temperature — re-zero at boot with no load. |
| LDR (GL5528 photoresistor) + 10 kΩ divider | any (passive) | 0.3 | Cheap and rugged, but non-linear and unit-to-unit variation is large. |
| 5 V single-channel opto-isolated relay module | 5 V coil | 70 | Cut the JD-VCC jumper and feed the coil separately for true isolation. |
| 0.96″ SSD1306 OLED display | 3.3–5 V | 20 | Static images burn in — invert or scroll the screen periodically. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna — the PA will destroy itself. |
| 5 V 3 A regulated SMPS adapter | 5 V | 3000 | Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A. |
Summed typical draw is 3380.3 mA. With a 1.5× design margin the supply should deliver at least 5100 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 |
| PubSubClient 2.8 | Lightweight MQTT 3.1.1 client for constrained devices. | Library Manager → "PubSubClient" by Nick O'Leary |
| Adafruit SSD1306 + GFX 2.5.x | Framebuffer and text/graphics primitives for the OLED. | Library Manager → "Adafruit SSD1306" |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager → "LoRa" by Sandeep Mistry |
| NTPClient / configTime bundled | Wall-clock time from an NTP server for timestamping. | Bundled (`configTime()` on ESP32) |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager → "ArduinoJson" by Benoit Blanchon |
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 |
|---|---|---|---|
| Light sensor (LDR) | AOUT | GPIO 34 | Ambient (dusk/dawn backup) |
| Current sensor | AOUT | GPIO 35 | Lamp current (fault/power) |
| Dimming (0-10V/PWM) | ctrl | GPIO 25 | Brightness |
| Relay/contactor | IN | GPIO 26 | On/off |
| LoRa | SPI | GPIO 18/19/23/5 | Central control/report |
| OLED | SDA/SCL | GPIO 21/22 | Status |
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
- Each light accepts central commands (brightness/schedule/group) over LoRa/mesh and reports its status back.
- Sense lamp current to report power and detect a failed lamp (commanded on, drawing nothing).
- Drive the luminaire's dimming input (0-10V/PWM/DALI) for central brightness control; match the luminaire.
- Keep local dusk/dawn and minimum-level/fail-safe behaviour so a lost network never darkens a street unsafely.
- Integrate with the city CMS and gateway; respect the lighting infrastructure.
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
A city's streetlight estate is a huge, distributed asset that is traditionally managed with almost no information and no remote control, and the two consequences of that — you cannot change anything without visiting every pole, and you do not know a light has failed until someone tells you — are exactly what networking the lights fixes. Connecting every light to a central management system replaces "send a van" with "send a command" and replaces "wait for a complaint" with "the system already knows". Those two shifts — central control and proactive fault awareness — are the entire value proposition.
Central control means brightness and schedules become software, applied at any scale. A single light, a street, a zone, or the whole city can be re-dimmed or re-scheduled from the management system — to implement a night-time dimming policy, brighten an area for an event, or respond to a change — without touching hardware. Group and city-wide commands make policy changes trivial, and the same channel lets the city verify that lights actually applied the change. This turns a static, unadjustable estate into one that can be operated.
The higher-value shift is proactive fault reporting. Each light reports its own state — on/off, brightness, power draw — and, critically, whether it has failed: a light commanded on that draws no current is a dead lamp, and it says so, with its location. This inverts maintenance. Instead of the city being blind (dark spots persist for weeks until a resident complains, and crews drive around inspecting working lights to find the broken ones), the management system holds a live, accurate picture of every fault, so maintenance is targeted — dispatch a crew straight to the lights that are actually out. Faster repairs, no wasted inspection driving, and dark spots fixed before they become safety and complaint problems.
Networked at city scale over a low-power technology (LoRa) or a mesh, all of this becomes a live map and an analytics platform: energy consumption, fault rates, lamp burn-hours (predicting end-of-life replacements), and policy compliance across the whole estate. The design keeps the non-negotiables of street lighting intact — safety and minimum lighting levels are respected, and the node behaves fail-safe (holding local dusk-to-dawn and never dimming below the safe minimum, staying on if the network is lost) so central control can never accidentally darken a street. It is candid that a real deployment integrates with the lighting infrastructure and a proper CMS, and that reliable current sensing underpins fault detection. But the core contribution is a genuine, widely-proven smart-city win: turn a sprawling, unmanaged, complaint-driven lighting estate into a centrally-controlled, self-reporting network that operates at a click and fixes its own dark spots before the city hears about them.
The maths behind it
Fault detection
Commanded ON but the lamp draws no current → failed:
fault if (commanded_on AND I_lamp < I_min)
report fault WITH location → proactive, targeted repair.
Also report I_lamp as power for energy analytics.
Group / city-wide command
A command targets a scope:
{scope: light|street|zone|city, brightness/schedule}
each node in scope applies it and ACKs
city-wide policy = one command, verified by ACKs.
Safety / fail-safe
Local invariants the network cannot override:
brightness ≥ minimum safe level (road type)
hold local dusk-to-dawn if command/network lost
fault/network loss → stay ON (fail-safe), never dark.
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 networked node
Each light: dimming interface, current sensing (fault/power), a light sensor (dusk/dawn backup), and a LoRa/mesh radio for central commands and status reporting.
Keep local dusk/dawn, minimum-level and fail-safe behaviour independent of the network.
Set up the network and CMS
Aggregate nodes via a gateway to a central management system that maps and controls all lights and collects status/faults.
Enable control, fault reporting and analytics
Support per-light/group/city commands with ACKs, proactive located fault reporting, and analytics (energy, faults, burn-hours).
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.
Apply commands, report status and faults
Apply central brightness/schedule (within safety), sense current, report status, and report a located fault when a commanded-on light draws no current.
cppstreetlight-node.ino#define B_MIN 40 // % minimum safe brightness #define I_MIN 0.05f // A: below when on = fault int commandedBrightness = 100; bool commandedOn = true; void applyCommand(int brightness, bool on){ commandedOn = on; commandedBrightness = max(brightness, on ? B_MIN : 0); // never below safe min setBrightness(commandedBrightness); digitalWrite(PIN_RELAY, commandedBrightness>0 ? HIGH : LOW); } const char* reportStatus(float iLamp, char* out, size_t n){ const char* fault = (commandedBrightness>0 && iLamp < I_MIN) ? "lamp failed" : nullptr; float watts = (commandedBrightness/100.0f) * LAMP_W; // via current in reality snprintf(out, n, "{\"id\":\"%s\",\"on\":%s,\"bright\":%d,\"W\":%.0f,\"fault\":\"%s\"}", LIGHT_ID, commandedBrightness>0?"true":"false", commandedBrightness, watts, fault?fault:"none"); return fault; }commandedBrightness = max(brightness, on ? B_MIN : 0); // never below safe minA central command is clamped to the minimum safe brightness, so remote control can never dim a street below its safe level.const char* fault = (commandedBrightness>0 && iLamp < I_MIN)A commanded-on light drawing no current is a failed lamp — proactive fault detection from current sensing.reportStatus(Each light reports its on/brightness/power and fault state, so the CMS always knows the true condition of the estate.return fault;The fault (with the light's id/location) is surfaced so maintenance can be dispatched straight to the dark light before anyone complains.Distribute commands and manage centrally
Accept per-light/group/city commands with ACKs, report status/faults to the CMS, and drive the live map, policy and analytics; hold fail-safe on network loss.
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.
/* ═══════════════════════════════════════════════════════════════
Smart Streetlight Network — ESP32 node
Central brightness/schedule control (per light/group/city) with ACKs,
per-light status and PROACTIVE located fault reporting (failed lamp).
Local dusk/dawn, minimum-level and fail-safe (on) independent of the
network. LoRa to a city management system.
══════════════════════════════════════════════════════════════════ */
#include <WiFi.h>
#include <PubSubClient.h>
#include <LoRa.h>
#include <SPI.h>
#define PIN_ILAMP 35
#define PIN_DIM 25
#define PIN_RELAY 26
#define B_MIN 40
#define I_MIN 0.05f
const char *LIGHT_ID = "SL-8842";
const char *ZONE = "Z4";
int brightness = 100; bool on = true; bool networkOk = true;
WiFiClient net; PubSubClient mqtt(net);
void setBrightness(int pct){ ledcWrite(0, pct*1023/100); }
void applyCommand(int b, bool o){
on = o; brightness = o ? max(b, B_MIN) : 0; // never below safe minimum
setBrightness(brightness);
digitalWrite(PIN_RELAY, brightness>0?HIGH:LOW);
}
void onCommand(const char* payload){
// parse {scope,target,brightness,on} and apply if this node is in scope
int b; bool o; if (commandForMe(payload, ZONE, LIGHT_ID, b, o)){
applyCommand(b, o);
mqtt.publish("light/ack", LIGHT_ID); // ACK so CMS can verify
}
}
void setup(){
Serial.begin(115200);
pinMode(PIN_RELAY, OUTPUT);
ledcSetup(0,1000,10); ledcAttachPin(PIN_DIM,0);
SPI.begin(); LoRa.setPins(5,14,2); LoRa.begin(433E6);
WiFi.begin(WIFI_SSID,WIFI_PASS); mqtt.setServer(MQTT_HOST,1883);
mqtt.setCallback([](char*,byte*p,unsigned int l){
char b[128]; memcpy(b,p,min(l,127u)); b[min(l,127u)]=0; onCommand(b); });
}
void loop(){
networkOk = (WiFi.status()==WL_CONNECTED) && mqtt.connected();
if (!networkOk && WiFi.status()==WL_CONNECTED) mqtt.connect("light-1");
if (mqtt.connected()){ mqtt.subscribe("light/cmd"); mqtt.loop(); }
// fail-safe: if network lost, hold local dusk/dawn at safe level (never dark)
if (!networkOk) applyCommand(B_MIN, true); // stay lit at safe minimum
float iLamp = analogRead(PIN_ILAMP)/4095.0f * I_FS;
const char* fault = (brightness>0 && iLamp < I_MIN) ? "lamp failed" : nullptr;
float watts = iLamp * MAINS_V; // power for analytics
char m[200];
snprintf(m,sizeof m,
"{\"id\":\"%s\",\"zone\":\"%s\",\"on\":%s,\"bright\":%d,"
"\"W\":%.0f,\"fault\":\"%s\"}",
LIGHT_ID, ZONE, brightness>0?"true":"false", brightness, watts,
fault?fault:"none");
LoRa.beginPacket(); LoRa.print(m); LoRa.endPacket();
if (mqtt.connected()){ mqtt.publish("light/status", m);
if (fault) mqtt.publish("light/fault", m); }
delay(5000);
}
Configuration & Calibration
Configuration steps
- Configure the dimming interface, current sensing, and the LoRa/mesh network and gateway/CMS.
- Set the minimum safe brightness and local dusk/dawn/fail-safe behaviour.
- Configure command scopes (light/group/city) with ACKs and status/fault reporting.
- Set analytics (energy, faults, burn-hours) at the CMS.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Fault/power sensing
Calibrate current sensing so a failed lamp is detected and power is accurate for analytics.
Control/ACK
Verify per-light/group/city commands apply and ACK; confirm minimum-level clamping.
Fail-safe
Confirm the node stays safely lit on network loss and holds local dusk/dawn.
Network Architecture & Connectivity
Communication protocol
Nodes accept scoped commands (light/group/city) with ACKs and report status/power/faults; the CMS controls policy and holds a live fault picture. Safety/fail-safe is local.
| Topic / endpoint | Direction | Payload |
|---|---|---|
light/cmd | CMS → nodes | scope, brightness/schedule |
light/status | node → CMS | on, brightness, power |
light/fault | node → maintenance | failed lamp (located) |
Message contract between the device and the broker.
Cloud platform configuration
A CMS maps and controls every light (group/city policy with ACK verification), holds a live fault picture for targeted maintenance, and provides energy/fault/burn-hour analytics.
Dashboard setup
A city lighting map with state/brightness/power, a fault list, group/city controls, and estate analytics.
Mobile app integration
Fault alerts (dark spots) and estate/energy summaries.
Security considerations
- Authenticate commands so only the city can control lights; keep safety/fail-safe local.
- Verify commands via ACKs; flag non-responding lights.
- Alert on light silence (possible fault).
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 |
|---|---|
| Send a group brightness command | Lights in scope dim/brighten and ACK; verifiable at CMS |
| Command below minimum | Clamped to the safe minimum |
| Disable a lamp | Located fault reported proactively |
| Drop the network | Light stays safely lit (fail-safe) on local dusk/dawn |
| City-wide schedule change | Applied and verified across the estate |
| Review analytics | Energy, fault rates, burn-hours available |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The CMS shows a live map of every light's state/brightness/power and faults, supports group/city control, and provides energy/fault/burn-hour analytics.
{
"id": "SL-8842",
"zone": "Z4",
"on": true,
"bright": 60,
"W": 82,
"fault": "none"
}
A light at 60% (per a central policy) reporting its power and no fault; a failed lamp would appear as a located fault in the CMS immediately, dispatching a crew before a resident notices the dark spot.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Report status/faults on a slow cadence, faults immediately; ACK commands.
- Keep safety/minimum-level/fail-safe local and independent of the network.
- Scale over LoRa/mesh; aggregate at the CMS.
- Provide estate analytics for management.
- 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
- Enforce minimum lighting levels and fail-safe (on) locally — central control must never darken a street unsafely, and network loss must not either.
- Lighting levels are standards-governed and policy is municipal; honour both.
- Integrate with the lighting infrastructure/CMS and mains safely (qualified power work).
- Fault-report dark spots for proactive maintenance.
- Mains voltage kills. Anything on the load side of the relay is at 230 V. Do not work on a powered circuit, and never leave exposed mains wiring on a bench where someone could touch it.
- Keep at least 6 mm of creepage between the mains and low-voltage sides of any board you make, and never route mains tracks under the microcontroller.
- Have a qualified electrician do the final installation into a consumer unit or wall fitting. In most jurisdictions this is a legal requirement, not a suggestion.
- Fit an RCD/RCBO upstream and fuse the load appropriately for its rating.
- 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
- Act on fault reports — proactive, targeted repair is the point.
- Verify control/ACK and fail-safe behaviour.
- Calibrate current/power sensing; check dimming.
- Review estate analytics (burn-hours) to plan replacements.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- 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 adaptive/motion dimming (as in the energy optimiser) within the network.
- Add predictive lamp end-of-life from burn-hours.
- Integrate with other smart-city assets on the poles.
- Add power-quality/energy analytics per light.
- 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.
- Smart street lighting / CMSReference
- Streetlight fault managementReference
- LoRa / mesh city networksReference
- LED dimming (0-10V/DALI)Reference
- Road lighting standardsReference