Siddhant Kumar
Project 081 · Smart City

Smart Streetlight Network.

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.

Intermediate 12–18 hours 29 min read LightingMeshCity
Jump to source Bill of materials
Smart Streetlight Network — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Intermediate
Build time
12–18 hours
Indicative cost
₹2,200 – ₹4,000 per light (+ CMS/gateway)
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Smart City
Last updated
28 July 2026
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.

An LED street light on a pole
Every streetlight on one network — centrally controlled and self-reporting. Photograph sourced from Wikimedia Commons — LED street light.jpg. Reused under the licence stated on that page; please check it before republishing.

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

SettingHow it is used
Municipal lighting managementCentral control and fault reporting across a city's streetlights.
Proactive maintenanceKnowing which lights are out, where, before residents complain.
Policy/energy managementCity-wide brightness/schedule changes and energy analytics.
Campus / estate lightingCentrally-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

AttributeValue
Difficulty levelIntermediate
Estimated completion time12–18 hours
Indicative build cost₹2,200 – ₹4,000 per light (+ CMS/gateway)
Primary disciplineSmart City
Reference platformESP32 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.

ComponentKey specificationQtyApprox. 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 DAC1₹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 isolation1₹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 nm1₹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 input1₹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²C1₹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–SF121₹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 protection1₹350
Dimmable driver interface
Match to the luminaire
0-10V/PWM/DALI to the luminaire driver1₹600
Current sensor (fault)Confirms the lamp draws current (fault detection)1₹200
LoRa/mesh module + gatewayNode radio and city gateway/CMS1₹900
Central management system
Software/platform
CMS mapping/controlling all lights and faults1

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

PartSpecificationSupplyInterfaceReference
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 DAC3.3 V logic / 5 V USBUART, SPI, I²C, I²S, CAN, PWMDatasheet
ACS712 hall-effect current sensor (20 A)±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation5 VAnalogueDatasheet
LDR (GL5528 photoresistor) + 10 kΩ divider10–20 kΩ at 10 lx, 1 MΩ dark, peak response 540 nmany (passive)Analogue dividerDatasheet
5 V single-channel opto-isolated relay moduleSPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input5 V coilDigital (active-low)Datasheet
0.96″ SSD1306 OLED display128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C3.3–5 VI²C (0x3C)Datasheet
SX1278 LoRa 433 MHz module (Ra-02)−148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF123.3 VSPIDatasheet
5 V 3 A regulated SMPS adapter100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection5 VDC barrel / USBDatasheet

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.

LoadSupply railTypical current (mA)Notes
ESP32 DevKit V1 (ESP-WROOM-32)3.3 V logic / 5 V USB160Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
ACS712 hall-effect current sensor (20 A)5 V10Zero-offset drifts with temperature — re-zero at boot with no load.
LDR (GL5528 photoresistor) + 10 kΩ dividerany (passive)0.3Cheap and rugged, but non-linear and unit-to-unit variation is large.
5 V single-channel opto-isolated relay module5 V coil70Cut the JD-VCC jumper and feed the coil separately for true isolation.
0.96″ SSD1306 OLED display3.3–5 V20Static images burn in — invert or scroll the screen periodically.
SX1278 LoRa 433 MHz module (Ra-02)3.3 V120Never power the radio without an antenna — the PA will destroy itself.
5 V 3 A regulated SMPS adapter5 V3000Measure 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.json under 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 dialout group: sudo usermod -aG dialout $USER and 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

LibraryWhy it is neededInstall
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager → "PubSubClient" by Nick O'Leary
Adafruit SSD1306 + GFX 2.5.xFramebuffer and text/graphics primitives for the OLED.Library Manager → "Adafruit SSD1306"
LoRa (sandeepmistry) 0.8.0SX127x radio configuration, packet TX/RX and callbacks.Library Manager → "LoRa" by Sandeep Mistry
NTPClient / configTime bundledWall-clock time from an NTP server for timestamping.Bundled (`configTime()` on ESP32)
ArduinoJson 7.xZero-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.

Smart Streetlight Network — system block diagramFunctional block diagram of the Smart Streetlight Network system. NodeLight controlbrightness/onCurrentfault/powerNetworkLoRa/meshcommands + reportsGatewayto CMSManageCMScentral controlFault mapproactiveActPolicygroup/cityMaintenancetargetedrightrightnone
Smart Streetlight Network — system block diagram

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.

Smart Streetlight Network — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBLight sensor (LDR)GPIO 34Ambient (dusk/dawnbackup)Current sensorGPIO 35Lamp current(fault/power)Dimming (0-10V/PWM)GPIO 25BrightnessRelay/contactorGPIO 26On/offLoRaGPIO 18/19/23/5Centralcontrol/reportOLEDGPIO 21/22Status
Smart Streetlight Network — wiring schematic
PeripheralPeripheral pinController pinSignal
Light sensor (LDR)AOUTGPIO 34Ambient (dusk/dawn backup)
Current sensorAOUTGPIO 35Lamp current (fault/power)
Dimming (0-10V/PWM)ctrlGPIO 25Brightness
Relay/contactorINGPIO 26On/off
LoRaSPIGPIO 18/19/23/5Central control/report
OLEDSDA/SCLGPIO 21/22Status

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.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
ESP32 node applying central brightness commands and detecting a failed lamp from its current. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

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.

Smart Streetlight Network — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · ACS712 hall-effect current sensor (20 A)Driver layerwifi · pubsub · ssd1306 · lorolibApplication logicsampling loop · filtering · thresholds · state machineTransport layerLoRa/mesh → city lighting CMS · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
Smart Streetlight Network — architecture stack

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

plainFault 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

plainGroup / 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

plainSafety / 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.

Smart Streetlight Network — firmware flowchartControl flow through the main program loop. Node: apply command /dusk-dawnSet brightness/on; sensecurrentCommanded on but nocurrent? → faultReport located faultReport statusReport located faultReport statusCMS: map, control, analyticsPush policy; dispatchmaintenance
Smart Streetlight Network — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. 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.

  2. 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.

  3. 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.

  1. 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.
  2. 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.

cppsmart-streetlight-network.ino
/* ═══════════════════════════════════════════════════════════════
   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);
}
void onCommand(const char* payload)The node applies central commands addressed to its light/zone/city scope and ACKs, so a city-wide policy change is one command the CMS can verify per light.
brightness = o ? max(b, B_MIN) : 0; // never below safe minimumCentral brightness is clamped to the safe minimum, so remote control can never dim a street below its safe level.
if (!networkOk) applyCommand(B_MIN, true); // stay lit at safe minimumIf the network is lost, the light fails safe — staying lit at the safe minimum on local dusk/dawn rather than going dark.
const char* fault = (brightness>0 && iLamp < I_MIN)A commanded-on light drawing no current is a failed lamp, reported proactively with its id/zone for targeted maintenance.
if (fault) mqtt.publish("light/fault", m)Faults are pushed to the CMS the moment they occur, so the city knows which lights are out before any resident complains.

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.

  1. Fault/power sensing

    Calibrate current sensing so a failed lamp is detected and power is accurate for analytics.

  2. Control/ACK

    Verify per-light/group/city commands apply and ACK; confirm minimum-level clamping.

  3. Fail-safe

    Confirm the node stays safely lit on network loss and holds local dusk/dawn.

Network Architecture & Connectivity

Smart Streetlight Network — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsLight nodeESP32Other lightscity estateLoRa / meshCity gatewayto CMSMQTTLighting CMScontrol + faultsCMSmap/controlMaintenancefaults
Smart Streetlight Network — network topology

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 / endpointDirectionPayload
light/cmdCMS → nodesscope, brightness/schedule
light/statusnode → CMSon, brightness, power
light/faultnode → maintenancefailed 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.

TestWhat you should see
Send a group brightness commandLights in scope dim/brighten and ACK; verifiable at CMS
Command below minimumClamped to the safe minimum
Disable a lampLocated fault reported proactively
Drop the networkLight stays safely lit (fail-safe) on local dusk/dawn
City-wide schedule changeApplied and verified across the estate
Review analyticsEnergy, 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.

jsonlight.json
{
  "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.

A city skyline at night
A CMS maps and controls the whole estate and knows every dark spot before a resident complains. Photograph sourced from Wikimedia Commons — Smart city.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Faults not known until complaints

Likely cause. No fault reporting

Fix. Sense current; report commanded-on-but-no-current as a located fault

Policy changes need site visits

Likely cause. No central control

Fix. Network lights to a CMS; use group/city commands with ACKs

Street darkened unsafely

Likely cause. Central control below safe minimum

Fix. Clamp to minimum level locally; fail safe (on) on network loss

Commands not verified

Likely cause. No ACK

Fix. ACK commands; flag non-responding (possibly failed) lights

Power analytics wrong

Likely cause. Uncalibrated current sensing

Fix. Calibrate current/power sensing

The sketch will not upload — "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools → Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it — it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 µF electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise — shorten the cable and keep motor wiring away from the USB lead.

An I²C device is not detected

Likely cause. Wrong address, missing pull-ups, swapped SDA/SCL, or a bus too long for the pull-up value.

Fix. Run an I²C scanner sketch first — it should print the device address. Most breakout boards include 4.7 kΩ pull-ups, but if you have chained four of them the parallel resistance is too low; remove the pull-ups from all but one board. Keep the bus under 30 cm at 100 kHz.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device — check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

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 a millis() 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_t where 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

What are the two big wins?

Central control — changing brightness/schedules across the city from software instead of visiting every pole — and proactive fault reporting: the system knows a light has failed (and where) the instant it happens, instead of waiting for a resident to complain.

How does it know a light has failed?

By sensing whether the lamp actually draws current when commanded on. A commanded-on light drawing nothing is a dead lamp, reported with its location so maintenance goes straight to it.

Can central control accidentally darken a street?

No — the node clamps any command to the road's minimum safe brightness and fails safe (stays lit at the safe minimum on local dusk/dawn) if the network is lost. Safety is enforced locally, independent of the network.

How does it change maintenance?

From reactive and blind to proactive and targeted. Instead of crews driving around inspecting working lights to find broken ones, the CMS shows exactly which lights are out, where — so crews are dispatched straight to them.

How does it scale to a whole city?

Lights network over a low-power technology (LoRa) or a mesh to a central management system that maps and controls the whole estate and holds a live fault picture and analytics.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Smart street lighting / CMSReference
  2. Streetlight fault managementReference
  3. LoRa / mesh city networksReference
  4. LED dimming (0-10V/DALI)Reference
  5. Road lighting standardsReference