Close Menu
  • Articles
    • Learn Electronics
    • Product Review
    • Tech Articles
  • Electronics Circuits
    • 555 Timer Projects
    • Op-Amp Circuits
    • Power Electronics
  • Microcontrollers
    • Arduino Projects
    • STM32 Projects
    • AMB82-Mini IoT AI Camera
    • BLE Projects
  • IoT Projects
    • ESP8266 Projects
    • ESP32 Projects
    • ESP32 MicroPython
    • ESP32-CAM Projects
    • LoRa/LoRaWAN Projects
  • Raspberry Pi
    • Raspberry Pi Projects
    • Raspberry Pi Pico Projects
    • Raspberry Pi Pico W Projects
  • Electronics Calculator
Facebook X (Twitter) Instagram
  • About Us
  • Disclaimer
  • Privacy Policy
  • Contact Us
  • Advertise With Us
Facebook X (Twitter) Instagram Pinterest YouTube LinkedIn
How To Electronics
  • Articles
    • Learn Electronics
    • Product Review
    • Tech Articles
  • Electronics Circuits
    • 555 Timer Projects
    • Op-Amp Circuits
    • Power Electronics
  • Microcontrollers
    • Arduino Projects
    • STM32 Projects
    • AMB82-Mini IoT AI Camera
    • BLE Projects
  • IoT Projects
    • ESP8266 Projects
    • ESP32 Projects
    • ESP32 MicroPython
    • ESP32-CAM Projects
    • LoRa/LoRaWAN Projects
  • Raspberry Pi
    • Raspberry Pi Projects
    • Raspberry Pi Pico Projects
    • Raspberry Pi Pico W Projects
  • Electronics Calculator
How To Electronics
Home » Build IoT DC Energy Meter with ESP32 Web Dashboard
ESP32 Projects IoT Projects

Build IoT DC Energy Meter with ESP32 Web Dashboard

Mamtaz AlamBy Mamtaz AlamUpdated:September 5, 20257 Mins Read
Share Facebook Twitter LinkedIn Telegram Reddit WhatsApp
Build IoT DC Energy Meter with ESP32 Web Dashboard
Share
Facebook Twitter LinkedIn Pinterest Email Reddit Telegram WhatsApp

Overview

In this project, we will build an IoT DC Energy Meter using an ESP32, which can measure voltage, current, power, and energy consumption in real time and display the results on a dynamic web dashboard. Earlier we build a simple DC Energy Meter with Arduino.

I recently came across a neat custom-made DC Power Meter Module from Mutex Embedded. This compact yet powerful board can measure DC parameters such as voltage (0–32 V range), current (up to 5 A), power, and cumulative energy consumption. It communicates easily over the I²C protocol, making it ideal for integration with the ESP32 for IoT applications.

For this guide, I’ll show you how to connect the module to an ESP32 board and set up a web server that displays live energy data. The webpage, built with HTML, CSS, and JavaScript, automatically updates the readings using AJAX, so you can view voltage, current, power, and energy in real time on any device connected to the same Wi-Fi network.

This project is a simple yet very practical way to create an IoT-enabled DC power monitoring system with ESP32. Whether you’re working on electronics, battery-powered projects, or renewable energy experiments, this solution makes it easy to monitor your system’s energy usage remotely through a clean and responsive web interface.


Bill of Materials

We need the following components to build this IoT DC Energy meter with ESP32.

S.N.Components NameQuantityPurchase Link
1ESP32 Board1Amazon | AliExpress
2DC Power Monitor Module1Mutex-Embedded
3Connecting Wires10Amazon | AliExpress
4Breadboard1Amazon | AliExpress




DC Power Monitor Module

The DC Power Monitoring Module is a feature-rich solution for tracking voltage, current, power, and energy consumption across four independent channels. At its core is the ESP32-S2 microcontroller, ensuring high performance and IoT readiness. Current measurement is handled through precision high-wattage shunt resistors, while an onboard SD card slot enables real-time data logging. A CR1220 coin cell holder powers the real-time clock, allowing accurate time-stamping of measurements even when the main supply is off.

On the hardware side, the board includes a tiny rear connector that works with a dedicated power extension board, which provides a USB-to-serial interface, micro-USB port, and reset switch. With support for up to 32 V input voltage and 5 A current per channel, the module is well-suited for monitoring small to medium DC appliances or battery systems.

The module supports both I²C and Modbus RTU (UART/RS485) interfaces, making it compatible with a wide range of microcontrollers, including Arduino and ESP32. Data can also be logged to the SD card for up to a year, with weekly historical summaries available for long-term analysis. To simplify user experience, a free PC application is provided for configuration, live monitoring, and firmware updates.

Features & Specifications

  • Voltage range: 0 – 32V
  • Current range 0 – 5A
  • 4 – Channels with common GND
  • Serial Interface: I2C + UART Modbus
  • RS-485 Bus extension
  • Real-Time Clock
  • Micro SD Card Data Logging
  • PC Application
  • Built for Industrial use

Useful Links

  1. DC Power Monitor Module
  2. DC Power Monitor Extension
  3. Datasheet/User Manual
  4. PC Application Software

Building an IoT DC Energy Meter with ESP32

The DC Power Monitor Module supports both I²C and Modbus RTU communication protocols. In this project, we will be using the I²C interface to connect the module with an ESP32 and build our own IoT-enabled DC Energy Meter.

Hardware & Circuit

Circuit for IoT DC Energy Meter with ESP32

The wiring is very straightforward. Connect the VCC pin of the Power Monitor Module to the 3.3V pin on the ESP32, and connect the GND pin to the ESP32 GND. Then, link the communication lines by connecting the module’s SDA pin to the ESP32’s default GPIO 21 and the SCL pin to GPIO 22. These are the default I²C pins on most ESP32 development boards.

ESP32 DC Power Meter Module Connection

With just these four simple connections, the ESP32 can read voltage, current, power, and energy data directly from the module over the I²C bus and then serve the data on a local web dashboard.

Power Supply & Load Connection

To start using the DC Power Monitor Module, a DC source and a load need to be connected. The module provides four independent input/output channels labeled IN1–IN4 and OUT1–OUT4. For demonstration purposes, we will work with Channel 1.

Connecting the Power Source
You can use any suitable DC supply, such as a battery pack or a regulated DC adapter. Connect the positive terminal of the supply to the IN1 terminal, while the negative (ground) terminal should be linked to the GND pin on the module.

Connecting the Load
Next, connect the positive terminal of your load (for example, a DC motor, LED lamp, or another electronic circuit) to the OUT1 terminal. The negative terminal of the load must also be tied to the common ground of the module.

Once these simple connections are made, the module will continuously measure the input voltage and the current flowing through the load, and from these values, it will automatically calculate the power and accumulated energy for Channel 1.




I2C Library (MbI2C_ArduinoPort.h)

The developer of the DC Power Monitor Module has already provided a library to interface with this device. Although the original library was created for another microcontroller platform, it can be easily adapted for the ESP32 to communicate with the module over the I²C bus. This allows us to read real-time values of voltage, current, power, and energy directly from the module without building everything from scratch.

To get started, create a folder anywhere on your computer and name it iot_energy_meter. Inside this folder, create a new text file and save it as MbI2C_ArduinoPort.h. Copy the provided library code into this file and save it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
#include <Arduino.h>
#include <Wire.h>
#include <stdint.h>
#include <string.h>
 
// -------- Original mb_i2c.h content (with include-guard adapted) --------
typedef bool     (*I2cWriteCallback_t)  (uint8_t slaveAddrs, uint8_t* pDataWr, uint8_t size);
typedef bool     (*I2cReadCallback_t)   (uint8_t slaveAddrs, uint8_t* pDataRd, uint8_t size);
typedef uint32_t (*GetMillisCallback_t) (void);
typedef void     (*DelayCallback_t)     (uint32_t msDelay);
 
typedef struct
{
  uint8_t             i2cAddress;
  I2cWriteCallback_t  cbWrite;
  I2cReadCallback_t   cbRead;
  GetMillisCallback_t cbGetMillis;
  DelayCallback_t     cbDelay;
} MbI2c_Init_t;
 
typedef struct
{
  MbI2c_Init_t init;
  bool         isInitialized;
} MbI2c_Handle_t;
 
typedef enum
{
  MbI2c_RegType_Holding,
  MbI2c_RegType_Input,
} MbI2c_RegType_e;
 
// -------- Constants from mb_i2c.c --------
#ifndef MB_I2C_READ_BUFFER_SIZE
#define MB_I2C_READ_BUFFER_SIZE  128
#endif
 
#ifndef MB_I2C_READ_DELAY
#define MB_I2C_READ_DELAY        5
#endif
 
#ifndef MB_I2C_READ_TIMEOUT
#define MB_I2C_READ_TIMEOUT      100
#endif
 
typedef enum
{
  MbI2c_Cmd_WriteHolding = 1,
  MbI2c_Cmd_ReadHolding  = 2,
  MbI2c_Cmd_ReadInput    = 3
} MbI2c_Cmd_e;
 
// SMBus CRC-8 lookup table
static const uint8_t gLookupTableSmbus[256] =
{
    0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,
    0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d,
    0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
    0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d,
    0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5,
    0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
    0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85,
    0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd,
    0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
    0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea,
    0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2,
    0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
    0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
    0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a,
    0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
    0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a,
    0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c,
    0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
    0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,
    0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4,
    0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
    0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44,
    0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c,
    0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
    0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b,
    0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63,
    0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
    0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13,
    0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb,
    0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83,
    0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb,
    0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3
};
 
static uint8_t gReadBuffer[MB_I2C_READ_BUFFER_SIZE];
 
// -------- Internal functions from mb_i2c.c --------
static uint8_t mbI2c_smbus_calculate (uint8_t* pData, uint16_t size)
{
  uint8_t crcByte = 0;
  for(uint16_t i = 0; i < size; i++)
  {
    crcByte = gLookupTableSmbus[crcByte ^ pData[i]];
  }
  return crcByte;
}
 
static bool mbI2c_smbus_verify (uint8_t* pData, uint16_t size, uint8_t crc)
{
  uint8_t crcByte = mbI2c_smbus_calculate(pData, size);
  return (crcByte == crc);
}
 
static bool mbI2c_readData (MbI2c_Handle_t* pHandle, uint16_t* pData, uint16_t len)
{
  uint8_t lenBytes  = len * 2;
  bool    i2cStatus = false;
 
  uint32_t startTicks = pHandle->init.cbGetMillis();
  do
  {
    if(pHandle->init.cbRead(pHandle->init.i2cAddress, gReadBuffer, lenBytes + 1))
    {
      uint8_t crcValue = gReadBuffer[lenBytes];
      bool crcState = mbI2c_smbus_verify(gReadBuffer, lenBytes, crcValue);
      if(crcState)
      {
        memcpy(pData, gReadBuffer, lenBytes);
        i2cStatus = true;
        break;
      }
      else
      {
        i2cStatus = false;
      }
    }
    else
    {
      i2cStatus = false;
    }
  } while ((pHandle->init.cbGetMillis() - startTicks) < MB_I2C_READ_TIMEOUT);
 
  return i2cStatus;
}
 
// -------- Public API from mb_i2c.c --------
static bool mbI2c_init (MbI2c_Init_t* pInit, MbI2c_Handle_t* pHandle)
{
  if((pInit->cbWrite != NULL) && (pInit->cbRead != NULL) &&
     (pInit->cbGetMillis != NULL) && (pInit->cbDelay != NULL))
  {
    memcpy(&pHandle->init, pInit, sizeof(MbI2c_Init_t));
    pHandle->isInitialized = true;
    return true;
  }
  pHandle->isInitialized = false;
  return false;
}
 
static bool mbI2c_writeHolding (MbI2c_Handle_t* pHandle, uint16_t regAddrs, uint16_t* pWriteBuf, uint16_t len)
{
  if(!pHandle->isInitialized)
  {
    return false;
  }
 
  bool i2cStatus = true;
  uint8_t cmdPacket[5] = {0};
  cmdPacket[0] = MbI2c_Cmd_WriteHolding;
  cmdPacket[1] = (regAddrs & 0xFF);
  cmdPacket[2] = ((regAddrs >> 8) & 0xFF);
  cmdPacket[3] = len;
  cmdPacket[4] = mbI2c_smbus_calculate(cmdPacket, 4);
 
  i2cStatus = pHandle->init.cbWrite(pHandle->init.i2cAddress, cmdPacket, 5);
 
  if(i2cStatus)
  {
    i2cStatus = pHandle->init.cbWrite(pHandle->init.i2cAddress, (uint8_t*)pWriteBuf, len * 2);
  }
 
  if(i2cStatus)
  {
    uint8_t crcValue = mbI2c_smbus_calculate((uint8_t*)pWriteBuf, len * 2);
    i2cStatus = pHandle->init.cbWrite(pHandle->init.i2cAddress, (uint8_t*)&crcValue, 1);
  }
 
  return i2cStatus;
}
 
static bool mbI2c_readHolding (MbI2c_Handle_t* pHandle, uint16_t regAddrs, uint16_t* pReadBuf, uint16_t len)
{
  if(!pHandle->isInitialized)
  {
    return false;
  }
 
  uint8_t cmdPacket[5] = {0};
  cmdPacket[0] = MbI2c_Cmd_ReadHolding;
  cmdPacket[1] = (regAddrs & 0xFF);
  cmdPacket[2] = ((regAddrs >> 8) & 0xFF);
  cmdPacket[3] = len;
  cmdPacket[4] = mbI2c_smbus_calculate(cmdPacket, 4);
 
  bool i2cStatus = pHandle->init.cbWrite(pHandle->init.i2cAddress, cmdPacket, 5);
  if(i2cStatus)
  {
    pHandle->init.cbDelay(MB_I2C_READ_DELAY);
    i2cStatus = mbI2c_readData(pHandle, pReadBuf, len);
  }
  return i2cStatus;
}
 
static bool mbI2c_readInput (MbI2c_Handle_t* pHandle, uint16_t regAddrs, uint16_t* pReadBuf, uint16_t len)
{
  if(!pHandle->isInitialized)
  {
    return false;
  }
 
  uint8_t cmdPacket[5] = {0};
  cmdPacket[0] = MbI2c_Cmd_ReadInput;
  cmdPacket[1] = (regAddrs & 0xFF);
  cmdPacket[2] = ((regAddrs >> 8) & 0xFF);
  cmdPacket[3] = len;
  cmdPacket[4] = mbI2c_smbus_calculate(cmdPacket, 4);
 
  bool i2cStatus = pHandle->init.cbWrite(pHandle->init.i2cAddress, cmdPacket, 5);
  if(i2cStatus)
  {
    pHandle->init.cbDelay(MB_I2C_READ_DELAY);
    i2cStatus = mbI2c_readData(pHandle, pReadBuf, len);
  }
  return i2cStatus;
}
 
static bool mbI2c_writeHoldingUint16 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, uint16_t data)
{
  return mbI2c_writeHolding(pHandle, regAddrs, &data, 1);
}
 
static bool mbI2c_writeHoldingInt16 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, int16_t data)
{
  return mbI2c_writeHolding(pHandle, regAddrs, (uint16_t*)&data, 1);
}
 
static bool mbI2c_writeHoldingUint32 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, uint32_t data)
{
  return mbI2c_writeHolding(pHandle, regAddrs, (uint16_t*)&data, 2);
}
 
static bool mbI2c_writeHoldingInt32 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, int32_t data)
{
  return mbI2c_writeHolding(pHandle, regAddrs, (uint16_t*)&data, 2);
}
 
static bool mbI2c_writeHoldingBool (MbI2c_Handle_t* pHandle, uint16_t regAddrs, bool data)
{
  uint16_t data16 = data;
  return mbI2c_writeHolding(pHandle, regAddrs, &data16, 1);
}
 
static bool mbI2c_writeHoldingFloat (MbI2c_Handle_t* pHandle, uint16_t regAddrs, float data)
{
  return mbI2c_writeHolding(pHandle, regAddrs, (uint16_t*)&data, 2);
}
 
static bool mbI2c_readUint16 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, uint16_t* pReadData, MbI2c_RegType_e regType)
{
  if(regType == MbI2c_RegType_Holding)
  {
    return mbI2c_readHolding(pHandle, regAddrs, pReadData, 1);
  }
  else
  {
    return mbI2c_readInput(pHandle, regAddrs, pReadData, 1);
  }
}
 
static bool mbI2c_readInt16 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, int16_t* pReadData, MbI2c_RegType_e regType)
{
  if(regType == MbI2c_RegType_Holding)
  {
    return mbI2c_readHolding(pHandle, regAddrs, (uint16_t*)pReadData, 1);
  }
  else
  {
    return mbI2c_readInput(pHandle, regAddrs, (uint16_t*)pReadData, 1);
  }
}
 
static bool mbI2c_readUint32 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, uint32_t* pReadData, MbI2c_RegType_e regType)
{
  if(regType == MbI2c_RegType_Holding)
  {
    return mbI2c_readHolding(pHandle, regAddrs, (uint16_t*)pReadData, 2);
  }
  else
  {
    return mbI2c_readInput(pHandle, regAddrs, (uint16_t*)pReadData, 2);
  }
}
 
static bool mbI2c_readInt32 (MbI2c_Handle_t* pHandle, uint16_t regAddrs, int32_t* pReadData, MbI2c_RegType_e regType)
{
  if(regType == MbI2c_RegType_Holding)
  {
    return mbI2c_readHolding(pHandle, regAddrs, (uint16_t*)pReadData, 2);
  }
  else
  {
    return mbI2c_readInput(pHandle, regAddrs, (uint16_t*)pReadData, 2);
  }
}
 
static bool mbI2c_readBool (MbI2c_Handle_t* pHandle, uint16_t regAddrs, bool* pReadData, MbI2c_RegType_e regType)
{
  uint16_t data16 = 0;
  bool status = true;
  if(regType == MbI2c_RegType_Holding)
  {
    status = mbI2c_readHolding(pHandle, regAddrs, &data16, 1);
  }
  else
  {
    status = mbI2c_readInput(pHandle, regAddrs, &data16, 1);
  }
  *pReadData = (bool)data16;
  return status;
}
 
static bool mbI2c_readFloat (MbI2c_Handle_t* pHandle, uint16_t regAddrs, float* pReadData, MbI2c_RegType_e regType)
{
  if(regType == MbI2c_RegType_Holding)
  {
    return mbI2c_readHolding(pHandle, regAddrs, (uint16_t*)pReadData, 2);
  }
  else
  {
    return mbI2c_readInput(pHandle, regAddrs, (uint16_t*)pReadData, 2);
  }
}
 
// -------- Arduino Wire() callbacks (plug into MbI2c_Init_t) --------
static bool ArduinoI2C_Write(uint8_t addr7, uint8_t* pDataWr, uint8_t size)
{
  Wire.beginTransmission(addr7);
  size_t n = Wire.write(pDataWr, size);
  uint8_t err = Wire.endTransmission(true); // send STOP
  return (err == 0) && (n == size);
}
 
static bool ArduinoI2C_Read(uint8_t addr7, uint8_t* pDataRd, uint8_t size)
{
  // One request for 'size' bytes; then pull them out.
  // Note: classic AVR Wire has a 32-byte rx buffer limit.
  uint32_t start = millis();
  uint8_t got = Wire.requestFrom((int)addr7, (int)size, (int)true);
  while ( (Wire.available() < got) && (millis() - start < MB_I2C_READ_TIMEOUT) )
  {
    // wait briefly if somehow not all are queued yet
    delay(1);
  }
  if (got < size) {
    // If the peripheral returned fewer bytes, fill what we have and fail
    for (uint8_t i=0; i<got; ++i) pDataRd[i] = (uint8_t)Wire.read();
    return false;
  }
  for (uint8_t i=0; i<size; ++i)
    pDataRd[i] = (uint8_t)Wire.read();
  return true;
}
 
static uint32_t ArduinoMillis() { return millis(); }
static void     ArduinoDelay(uint32_t ms) { delay(ms); }
 
// Helper to quickly set up a handle for Arduino Wire
static bool MbI2c_BeginArduino(MbI2c_Handle_t* pHandle, uint8_t i2cAddress)
{
  MbI2c_Init_t init;
  init.i2cAddress  = i2cAddress;
  init.cbWrite     = ArduinoI2C_Write;
  init.cbRead      = ArduinoI2C_Read;
  init.cbGetMillis = ArduinoMillis;
  init.cbDelay     = ArduinoDelay;
  return mbI2c_init(&init, pHandle);
}
 
// -------- Portable typed-read helpers (endianness & word-order control) -----
static inline uint16_t bswap16(uint16_t v) { return (uint16_t)((v >> 8) | (v << 8)); }
static inline void swap16(uint16_t& a, uint16_t& b) { uint16_t t=a; a=b; b=t; }
 
/*
* Many metering ICs return:
*  - 16-bit words big-endian (byte order inside each 16-bit is MSB first),
*  - and a 32-bit value as two 16-bit words that may be [HiWord, LoWord] or [LoWord, HiWord].
*
* Use these helpers to normalize into native CPU ordering for float/uint32.
*/
static bool mbRead2Words(MbI2c_Handle_t* h, uint16_t reg, MbI2c_RegType_e type,
                         bool swapBytesEachWord, bool swapWordOrder,
                         uint16_t outWords[2])
{
  uint16_t tmp[2] = {0,0};
  bool ok = (type == MbI2c_RegType_Holding)
            ? mbI2c_readHolding(h, reg, tmp, 2)
            : mbI2c_readInput(h, reg, tmp, 2);
  if (!ok) return false;
 
  // optional byte swap within each 16-bit
  if (swapBytesEachWord) {
    tmp[0] = bswap16(tmp[0]);
    tmp[1] = bswap16(tmp[1]);
  }
  // optional word swap for 32-bit values
  if (swapWordOrder) swap16(tmp[0], tmp[1]);
 
  outWords[0] = tmp[0];
  outWords[1] = tmp[1];
  return true;
}
 
static bool mbReadFloatPortable(MbI2c_Handle_t* h, uint16_t reg, MbI2c_RegType_e type,
                                bool swapBytesEachWord, bool swapWordOrder,
                                float* out)
{
  uint16_t w[2];
  if (!mbRead2Words(h, reg, type, swapBytesEachWord, swapWordOrder, w)) return false;
  memcpy(out, w, sizeof(w)); // now in native layout
  return true;
}
 
static bool mbReadUint32Portable(MbI2c_Handle_t* h, uint16_t reg, MbI2c_RegType_e type,
                                 bool swapBytesEachWord, bool swapWordOrder,
                                 uint32_t* out)
{
  uint16_t w[2];
  if (!mbRead2Words(h, reg, type, swapBytesEachWord, swapWordOrder, w)) return false;
  memcpy(out, w, sizeof(w));
  return true;
}
 
static bool mbReadUint16Portable(MbI2c_Handle_t* h, uint16_t reg, MbI2c_RegType_e type,
                                 bool swapBytesWord,
                                 uint16_t* out)
{
  uint16_t v = 0;
  if (!( (type == MbI2c_RegType_Holding)
        ? mbI2c_readHolding(h, reg, &v, 1)
        : mbI2c_readInput(h, reg, &v, 1))) return false;
 
  if (swapBytesWord) v = bswap16(v);
  *out = v;
  return true;
}




Code/Program: IoT DC Energy Meter with ESP32 WebServer

This following code turns the ESP32 into an IoT  smart DC energy meter that reads data from the DC Power Monitor Module over I²C. The ESP32 collects live measurements of voltage, current, power, and cumulative energy from Channel 1, processes the raw register values, and converts them into human-readable units. Sampling is done multiple times per second to ensure that the displayed values remain accurate and up to date.

At the same time, the ESP32 also runs as a Wi-Fi web server, hosting a modern, light-themed dashboard. This webpage, built with HTML, CSS, and JavaScript, uses AJAX to fetch new data every second without reloading. It shows the values in elegant cards with real-time sparklines, smooth animations, and a status bar for Wi-Fi and update timing. Anyone connected to the same Wi-Fi network can simply open the ESP32’s IP address in a browser to view live voltage, current, power, and energy readings in an attractive, dynamic interface.

From these lines change the WiFi SSID and password and replace with your network credentials.

1
2
3
// ===================== USER CONFIG =====================
#define WIFI_SSID  "********************"
#define WIFI_PASS  "********************"

After replacing the WiFi SSID and password, now you can upload the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/*
* ESP32 DC Energy Meter - AJAX Web UI (Light Theme)
* - Reads CH1 (V/I/W/Wh) via I2C
* - Hosts a WiFi web server serving a polished light-themed page
* - Page auto-refreshes values via AJAX (fetch /api) every second
* - Minimal sparklines for each metric
*/
 
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include "MbI2C_ArduinoPort.h"
 
// ===================== USER CONFIG =====================
#define WIFI_SSID  "********************"
#define WIFI_PASS  "********************"
 
// I2C meter address
static const uint8_t I2C_ADDR = 0x08;  // 7-bit default
 
// CH1 Input register addresses (decimal)
static const uint16_t REG_VOLTAGE = 80;   // U32 -> V
static const uint16_t REG_CURRENT = 82;   // U32 -> A
static const uint16_t REG_POWER   = 84;   // U32 -> W
static const uint16_t REG_ENERGY  = 88;   // U32 -> Wh
 
// All are Input registers
static const MbI2c_RegType_e VOLTAGE_TYPE = MbI2c_RegType_Input;
static const MbI2c_RegType_e CURRENT_TYPE = MbI2c_RegType_Input;
static const MbI2c_RegType_e POWER_TYPE   = MbI2c_RegType_Input;
static const MbI2c_RegType_e ENERGY_TYPE  = MbI2c_RegType_Input;
 
// Endianness (confirmed working for your board)
static const bool BYTES_BIG_ENDIAN_IN_WORDS = false;
static const bool SWAP_WORDS_32BIT          = false;
 
// Scaling
static const float VOLT_SCALE = 0.001f;
static const float CURR_SCALE = 0.001f;
static const float POWR_SCALE = 0.001f;
static const float ENER_SCALE = 0.001f;
 
// Sampling & API cadence
static const uint32_t SAMPLE_MS    = 500;
static const uint32_t API_CACHE_MS = 200;
// =======================================================
 
MbI2c_Handle_t gMeter;
WebServer server(80);
 
// Last readings
volatile float gV = 0, gA = 0, gW = 0, gE = 0;
volatile bool  gOkV = false, gOkA = false, gOkW = false, gOkE = false;
uint32_t lastSampleMs = 0;
 
// Build JSON cache
String lastJson;
uint32_t lastJsonMs = 0;
 
// ------------ Meter helpers ------------
bool readU32Scaled(uint16_t reg, MbI2c_RegType_e type, float scale, float& outVal) {
  uint32_t raw = 0;
  if (!mbReadUint32Portable(&gMeter, reg, type, BYTES_BIG_ENDIAN_IN_WORDS, SWAP_WORDS_32BIT, &raw)) {
    return false;
  }
  outVal = raw * scale;
  return true;
}
 
void sampleMeterIfDue() {
  uint32_t now = millis();
  if (now - lastSampleMs < SAMPLE_MS) return;
  lastSampleMs = now;
 
  float v=0, a=0, w=0, e=0;
 
  gOkV = readU32Scaled(REG_VOLTAGE, VOLTAGE_TYPE, VOLT_SCALE, v);
  gOkA = readU32Scaled(REG_CURRENT, CURRENT_TYPE, CURR_SCALE, a);
  gOkW = readU32Scaled(REG_POWER,   POWER_TYPE,   POWR_SCALE, w);
  gOkE = readU32Scaled(REG_ENERGY,  ENERGY_TYPE,  ENER_SCALE, e);
 
  if (gOkV) gV = v;
  if (gOkA) gA = a;
  if (gOkW) gW = w;
  if (gOkE) gE = e;
}
 
// ------------ Web content (Light theme) ------------
const char INDEX_HTML[] PROGMEM = R"HTML(
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>DC Energy Meter</title>
<style>
  :root{
    --bg1:#f7f9fc; --bg2:#eef2f7;
    --card:#ffffff; --border:#e6e9ef;
    --text:#1f2430; --muted:#6b7280;
    --accent:#0ea5e9; --accent-soft:#dff3fc;
    --ok:#16a34a; --warn:#ca8a04;
    --shadow: 0 12px 30px rgba(14, 53, 108, 0.10);
  }
  *{box-sizing:border-box}
  html,body{height:100%}
  body{
    margin:0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
    color:var(--text);
    background: radial-gradient(1200px 900px at 20% 10%, var(--bg2) 0%, var(--bg1) 60%);
    display:flex; align-items:center; justify-content:center; padding:22px;
  }
  .wrap{ width:min(1100px, 96vw); display:flex; flex-direction:column; gap:18px; }
  .header{ display:flex; align-items:center; justify-content:space-between; gap:16px; }
  h1{ margin:0; font-weight:800; letter-spacing:0.2px; font-size: clamp(20px, 2.5vw, 26px); display:flex; gap:10px; align-items:center; }
  .brand{ display:inline-flex; width:30px; height:30px; border-radius:10px; background:linear-gradient(145deg,#e8f6ff,#ffffff); border:1px solid var(--border); box-shadow: var(--shadow); align-items:center; justify-content:center; color:var(--accent); font-weight:900;}
  .pill{ border:1px solid var(--border); background:#ffffffaa; backdrop-filter:blur(6px); color:var(--muted); padding:8px 12px; border-radius:999px; font-size:12px; }
 
  .grid{ display:grid; gap:16px; grid-template-columns:repeat(4,1fr); }
  @media (max-width: 900px){ .grid{ grid-template-columns:repeat(2,1fr);} }
  @media (max-width: 520px){ .grid{ grid-template-columns:1fr; } }
 
  .card{
    position:relative; background:var(--card); border:1px solid var(--border); border-radius:18px; padding:16px 14px 12px 14px; box-shadow:var(--shadow);
  }
  .label{ font-size:12px; color:var(--muted); letter-spacing:.6px; text-transform:uppercase; display:flex; align-items:center; gap:8px; }
  .chip{ padding:4px 8px; border-radius:999px; font-size:11px; background:var(--accent-soft); color:#075985; }
  .value{ margin-top:6px; font-weight:800; font-size: clamp(24px, 4.5vw, 40px); line-height:1.1; letter-spacing:.3px; }
  .unit{ font-weight:700; font-size: clamp(12px,2vw,14px); color:var(--muted); margin-left:6px; }
  canvas{ width:100%; height:28px; display:block; margin-top:8px; }
  .footer{ display:flex; gap:10px; align-items:center; justify-content:flex-end; color:var(--muted); font-size:12px; }
  .badge{ color:var(--accent); font-weight:600; }
  .ok{ color:var(--ok); } .warn{ color:var(--warn); }
 
  /* subtle value animation */
  .value{ transition: transform .25s ease, text-shadow .25s ease; }
  .value.bump{ transform: translateY(-2px) scale(1.01); text-shadow: 0 2px 14px rgba(14,165,233,0.25); }
</style>
</head>
<body>
  <div class="wrap">
    <div class="header">
      <h1><span class="brand">⚡</span>DC Energy Meter</h1>
      <div class="pill"><span id="net">Connecting…</span></div>
    </div>
 
    <div class="grid">
      <div class="card">
        <div class="label">Voltage <span class="chip">Real-time</span></div>
        <div class="value" id="v">--.--<span class="unit">V</span></div>
        <canvas id="cv"></canvas>
      </div>
      <div class="card">
        <div class="label">Current <span class="chip">Real-time</span></div>
        <div class="value" id="i">--.--<span class="unit">A</span></div>
        <canvas id="ci"></canvas>
      </div>
      <div class="card">
        <div class="label">Power <span class="chip">Real-time</span></div>
        <div class="value" id="p">--.--<span class="unit">W</span></div>
        <canvas id="cp"></canvas>
      </div>
      <div class="card">
        <div class="label">Energy <span class="chip">Cumulative</span></div>
        <div class="value" id="e">--.--<span class="unit">Wh</span></div>
        <canvas id="ce"></canvas>
      </div>
    </div>
 
    <div class="footer">
      <span>Status:</span><span id="status" class="badge">starting…</span>
      <span>| Updated:</span><span id="ts">--:--:--</span>
    </div>
  </div>
 
<script>
  const $ = s=>document.querySelector(s);
  const ids = { v: $("#v"), i: $("#i"), p: $("#p"), e: $("#e") };
  const net = $("#net"), statusEl = $("#status"), ts = $("#ts");
 
  // sparkline buffers
  const N = 60;
  const buf = {
    v: Array(N).fill(0),
    i: Array(N).fill(0),
    p: Array(N).fill(0),
    e: Array(N).fill(0),
  };
  const cvs = {
    v: $("#cv"), i: $("#ci"), p: $("#cp"), e: $("#ce")
  };
 
  // Resize canvases to device pixels
  function fitCanvas(c){
    const r = window.devicePixelRatio || 1;
    const {width, height} = c.getBoundingClientRect();
    c.width = Math.max(120, Math.floor(width * r));
    c.height= Math.max(24,  Math.floor(height* r));
  }
  Object.values(cvs).forEach(fitCanvas);
  window.addEventListener('resize', ()=>Object.values(cvs).forEach(fitCanvas));
 
  function stamp(){
    const d=new Date(); const z=n=>String(n).padStart(2,'0');
    ts.textContent=`${z(d.getHours())}:${z(d.getMinutes())}:${z(d.getSeconds())}`;
  }
  function bump(el){ el.classList.add('bump'); setTimeout(()=>el.classList.remove('bump'), 220); }
 
  function drawSpark(c, data, accent="#0ea5e9"){
    const ctx = c.getContext('2d');
    const W = c.width, H = c.height, pad=6;
    ctx.clearRect(0,0,W,H);
    const min = Math.min(...data);
    const max = Math.max(...data);
    const lo = min === max ? min-1 : min;
    const hi = min === max ? max+1 : max;
    const scale = v => H - pad - ( (v-lo) * (H-2*pad) / (hi-lo) );
 
    // baseline
    ctx.strokeStyle = "#e6e9ef";
    ctx.lineWidth = 1;
    ctx.beginPath(); ctx.moveTo(0, H-pad); ctx.lineTo(W, H-pad); ctx.stroke();
 
    // line
    ctx.strokeStyle = accent;
    ctx.lineWidth = 2;
    ctx.beginPath();
    const step = W / (data.length-1);
    for(let i=0;i<data.length;i++){
      const x = i*step, y = scale(data[i]);
      i?ctx.lineTo(x,y):ctx.moveTo(x,y);
    }
    ctx.stroke();
 
    // fill fade
    const g = ctx.createLinearGradient(0,0,0,H);
    g.addColorStop(0, accent+"88");
    g.addColorStop(1, accent+"00");
    ctx.fillStyle = g;
    ctx.lineTo(W, H-pad); ctx.lineTo(0, H-pad); ctx.closePath(); ctx.fill();
  }
 
  function push(buf, v){ buf.push(v); if(buf.length>N) buf.shift(); }
 
  async function fetchAPI(){
    try{
      const r = await fetch('/api',{cache:'no-store'});
      if(!r.ok) throw new Error(r.statusText);
      const j = await r.json();
      const v = +(j.v ?? 0), i = +(j.i ?? 0), p = +(j.p ?? 0), e = +(j.e ?? 0);
 
      // numbers
      ids.v.firstChild.nodeValue = v.toFixed(3)+' ';
      ids.i.firstChild.nodeValue = i.toFixed(3)+' ';
      ids.p.firstChild.nodeValue = p.toFixed(3)+' ';
      ids.e.firstChild.nodeValue = e.toFixed(3)+' ';
      bump(ids.v); bump(ids.i); bump(ids.p); bump(ids.e);
 
      // status
      const allOk = j.okV && j.okA && j.okW && j.okE;
      statusEl.textContent = allOk ? 'OK' : 'Partial data';
      statusEl.className = allOk ? 'badge ok' : 'badge warn';
      stamp();
 
      // sparklines
      push(buf.v, v); push(buf.i, i); push(buf.p, p); push(buf.e, e);
      drawSpark(cvs.v, buf.v, "#0ea5e9");
      drawSpark(cvs.i, buf.i, "#06b6d4");
      drawSpark(cvs.p, buf.p, "#22c55e");
      drawSpark(cvs.e, buf.e, "#a855f7");
    }catch(e){
      statusEl.textContent='Fetch error'; statusEl.className='badge warn';
    }
  }
 
  async function fetchNet(){
    try{
      const r = await fetch('/net',{cache:'no-store'});
      if(!r.ok) throw new Error(r.statusText);
      const j = await r.json();
      net.textContent = j.ip ? `🟢 ${j.ssid} @ ${j.ip}` : 'WiFi?';
    }catch(e){
      net.textContent = 'WiFi?';
    }
  }
 
  fetchNet(); fetchAPI();
  setInterval(fetchAPI, 1000);
  setInterval(fetchNet, 5000);
</script>
</body>
</html>
)HTML";
 
// Handlers
void handleRoot() { server.send_P(200, "text/html; charset=utf-8", INDEX_HTML); }
 
void handleApi() {
  uint32_t now = millis();
  if (now - lastJsonMs > API_CACHE_MS) {
    float v = gV, a = gA, w = gW, e = gE;
    bool okV = gOkV, okA = gOkA, okW = gOkW, okE = gOkE;
 
    String s; s.reserve(128);
    s += F("{\"v\":"); s += String(v,6);
    s += F(",\"i\":"); s += String(a,6);
    s += F(",\"p\":"); s += String(w,6);
    s += F(",\"e\":"); s += String(e,6);
    s += F(",\"okV\":"); s += (okV?"true":"false");
    s += F(",\"okA\":"); s += (okA?"true":"false");
    s += F(",\"okW\":"); s += (okW?"true":"false");
    s += F(",\"okE\":"); s += (okE?"true":"false");
    s += F("}");
    lastJson = s; lastJsonMs = now;
  }
  server.send(200, "application/json; charset=utf-8", lastJson);
}
 
void handleNet() {
  String s; s.reserve(96);
  s += F("{\"ssid\":\""); s += WiFi.SSID();
  s += F("\",\"ip\":\""); s += WiFi.localIP().toString();
  s += F("\"}");
  server.send(200, "application/json; charset=utf-8", s);
}
 
void handleNotFound() { server.send(404, "text/plain", "Not found"); }
 
// ------------- Setup -------------
void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("\nESP32 DC Energy Meter WebServer (Light Theme)");
 
  // I2C / Meter
  Wire.begin();          // default pins (GPIO 21 SDA, 22 SCL) on many ESP32 dev boards
  Wire.setClock(400000);
  if (!MbI2c_BeginArduino(&gMeter, I2C_ADDR)) {
    Serial.println(F("Meter init failed!"));
  }
 
  // WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.printf("Connecting to WiFi: %s\n", WIFI_SSID);
  uint32_t t0 = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) {
    delay(250); Serial.print(".");
  }
  Serial.println();
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("WiFi OK, IP: "); Serial.println(WiFi.localIP());
  } else {
    Serial.println("WiFi failed; still serving (if connected later).");
  }
 
  // HTTP routes
  server.on("/", handleRoot);
  server.on("/api", handleApi);
  server.on("/net", handleNet);
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started.");
}
 
// ------------- Loop -------------
void loop() {
  sampleMeterIfDue();
  server.handleClient();
}




Testing the IoT DC Energy Meter

In the Arduino IDE, select your ESP32 development board from the Board Manager and choose the correct COM port. Click on the Upload button to flash the code onto the ESP32.

Once the upload is complete, open the Serial Monitor and set the baud rate to match the one defined in your sketch (e.g., 115200). The ESP32 will attempt to connect to your Wi-Fi network, and you should see the connection status and the assigned IP address printed in the Serial Monitor.

Make sure the DC Power Monitor Module is powered with a suitable DC supply such as a battery or a variable power supply. Also, connect a load to the selected channel—for example, an LED, a small DC motor, or any other DC appliance within the supported range.

After everything is connected, open a browser on any device that is on the same Wi-Fi network and type in the ESP32’s IP address shown in the Serial Monitor. This will open the live web dashboard, where you can see real-time values of voltage, current, power, and energy updating automatically.

At the same time, you can also access the same page on your mobile phone for convenient monitoring.

To test the system, try varying the input supply voltage or changing the load, and you’ll immediately observe the updates on the dashboard.

IoT DC Energy Meter using ESP32 Demo

Along with the numeric values, the page also shows graphical sparklines below each reading, giving you a quick visual representation of how the parameters change over time.

IoT DC Energy Meter ESP32 WebServer

This makes it easy to analyze your DC system at a glance, whether you’re on a computer or a mobile device.


Video Tutorial & Guide

IoT DC Energy Meter with ESP32 & Web Dashboard
Watch this video on YouTube.

Share. Facebook Twitter Pinterest LinkedIn Tumblr Email Reddit Telegram WhatsApp
Previous ArticleThe Future of Video Production: Adding Emotion with AI Voice Generators
Next Article DIY ESP32 Board for Battery Powered IoT Applications

Related Posts

ESP32 Fingerprint Attendance System with Live Web Dashboard

ESP32 Fingerprint Attendance System with Live Web Dashboard

Updated:June 21, 2026
IoT Based PM & Air Quality Monitoring System using ESP32

IoT Based PM & Air Quality Monitoring System using ESP32

Updated:June 14, 2026
DIY ESP32 MLX90640 IR Thermal Camera with Live Web Display

DIY ESP32 MLX90640 IR Thermal Camera with Live Web Display

Updated:May 10, 20262K
IoT Activity Tracker with ESP32 & Accelerometer Gyroscope

IoT Activity Tracker with ESP32 & Accelerometer/Gyroscope

Updated:May 2, 2026

ESP32 IoT Vehicle Motion Analyzer with MPU6050 & LIS3MDL

Updated:April 27, 20261K
High-Accuracy Pitch, Roll, Yaw with ESP32 & BNO08x IMU

High-Accuracy Pitch, Roll, Yaw with ESP32 & BNO08x IMU

Updated:April 27, 20262K
Add A Comment

CommentsCancel reply

Latest Posts
ESP32 Fingerprint Attendance System with Live Web Dashboard

ESP32 Fingerprint Attendance System with Live Web Dashboard

June 21, 2026
IoT Based PM & Air Quality Monitoring System using ESP32

IoT Based PM & Air Quality Monitoring System using ESP32

June 14, 2026
DIY ESP32 MLX90640 IR Thermal Camera with Live Web Display

DIY ESP32 MLX90640 IR Thermal Camera with Live Web Display

May 10, 2026
IoT Activity Tracker with ESP32 & Accelerometer Gyroscope

IoT Activity Tracker with ESP32 & Accelerometer/Gyroscope

May 2, 2026
A Guide to Sourcing Obsolete ICs for Vintage Projects

Beyond AliExpress: A Guide to Sourcing Obsolete ICs for Vintage Projects

April 21, 2026

ESP32 IoT Vehicle Motion Analyzer with MPU6050 & LIS3MDL

April 27, 2026
Building a Smart Sensor Node with a BLE Microcontroller

Building a Smart Sensor Node with a BLE Microcontroller

February 26, 2026
High-Accuracy Pitch, Roll, Yaw with ESP32 & BNO08x IMU

High-Accuracy Pitch, Roll, Yaw with ESP32 & BNO08x IMU

April 27, 2026
Top Posts & Pages
  • ESP32 Fingerprint Attendance System with Live Web Dashboard
    ESP32 Fingerprint Attendance System with Live Web Dashboard
  • IoT AC Energy Meter with PZEM-004T & ESP32 WebServer
    IoT AC Energy Meter with PZEM-004T & ESP32 WebServer
  • 12V DC to 220V AC Inverter Circuit & PCB
    12V DC to 220V AC Inverter Circuit & PCB
  • IoT Based ECG Monitoring with AD8232 ECG Sensor & ESP32
    IoT Based ECG Monitoring with AD8232 ECG Sensor & ESP32
  • ESP32 CAN Bus Tutorial | Interfacing MCP2515 CAN Module with ESP32
    ESP32 CAN Bus Tutorial | Interfacing MCP2515 CAN Module with ESP32
  • How to use INA226 DC Current Sensor with Arduino
    How to use INA226 DC Current Sensor with Arduino
  • How to use Modbus RTU with ESP32 to read Sensor Data
    How to use Modbus RTU with ESP32 to read Sensor Data
  • Half Wave Rectifier Basics, Circuit, Working & Applications
    Half Wave Rectifier Basics, Circuit, Working & Applications
Categories
  • Arduino Projects (197)
  • Articles (60)
    • Learn Electronics (19)
    • Product Review (15)
    • Tech Articles (28)
  • Electronics Circuits (46)
    • 555 Timer Projects (21)
    • Op-Amp Circuits (7)
    • Power Electronics (13)
  • IoT Projects (205)
    • ESP32 MicroPython (7)
    • ESP32 Projects (82)
    • ESP32-CAM Projects (15)
    • ESP8266 Projects (76)
    • LoRa/LoRaWAN Projects (22)
  • Microcontrollers (38)
    • AMB82-Mini IoT AI Camera (4)
    • BLE Projects (18)
    • STM32 Projects (19)
  • Raspberry Pi (93)
    • Raspberry Pi Pico Projects (57)
    • Raspberry Pi Pico W Projects (12)
    • Raspberry Pi Projects (24)
Follow Us
  • Facebook
  • Twitter
  • Pinterest
  • Instagram
  • YouTube
About Us

“‘How to Electronics’ is a vibrant community for electronics enthusiasts and professionals. We deliver latest insights in areas such as Embedded Systems, Power Electronics, AI, IoT, and Robotics. Our goal is to stimulate innovation and provide practical solutions for students, organizations, and industries. Join us to transform learning into a joyful journey of discovery and innovation.

Copyright © How To Electronics. All rights reserved.
  • About Us
  • Disclaimer
  • Privacy Policy
  • Contact Us
  • Advertise With Us

Type above and press Enter to search. Press Esc to cancel.

Ad Blocker Enabled!
Ad Blocker Enabled!
Looks like you're using an ad blocker. Please allow ads on our site. We rely on advertising to help fund our site.