- Install the Arduino IDE.
- Open the Arduino IDE.
- Click on the
Boards Managericon in the left navigation menu. - Search for
esp32and installesp32 by Esspressif Systems >3.3.3(if it fails, try again). - Plug your board into a USB port on your compupter.
- Select
ESP32 Family Devicefrom theSelect Boarddrop down at the top of the IDE OR Plug your device out and in to find the port if the name doesn't show and selectESP32 Dev Module. - Click on
Tools/Portand make sure a COM port is selected with the ESP32 Family Device. - Click on
Tools/Boardand selectESP32 Wrover Module - Make sure
Tools/Events Run OnandTools/Arduino Runs onare set to run onCore 0 - Set
Core Debug LeveltoInfo. - Avoid changing any other board settings at this time - if you have issues ask the host to double check your board config.
- Click on the
Uploadarrow at the top of the IDE to flash the empty script onto the baord. - Power cycle the board - the display should be blank.
- Replace the code with the following code and flash again:
void setup() { Serial.begin(9600); } void loop() { Serial.println("hello world"); delay(500); }
- Click on the
Serial Monitorπ icon at the top right of the IDE and make sure you seehello worldprinted out.
- Open example
File/Examples/ESP32/Camera/CameraWebServer. - In
CameraWebServer.inocomment out#include "board_config.h". - In
app_httpd.cppcomment out#include "board_config.h". - Paste the following after the #includes at the top of
CameraWebServer.ino: If your device has a mic.If your device has no mic.// Pins as per the board silk - note the diagram is incorrect. #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 4 #define SIOD_GPIO_NUM 18 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 36 #define Y8_GPIO_NUM 15 #define Y7_GPIO_NUM 12 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 35 #define Y4_GPIO_NUM 14 #define Y3_GPIO_NUM 13 #define Y2_GPIO_NUM 34 #define VSYNC_GPIO_NUM 5 #define HREF_GPIO_NUM 27 #define PCLK_GPIO_NUM 25 #define LED_GPIO_NUM -1
// Pins as per the diagram. #define PWDN_GPIO_NUM 26 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 32 #define SIOD_GPIO_NUM 13 #define SIOC_GPIO_NUM 12 #define Y9_GPIO_NUM 39 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 23 #define Y6_GPIO_NUM 18 #define Y5_GPIO_NUM 15 #define Y4_GPIO_NUM 4 #define Y3_GPIO_NUM 14 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 27 #define HREF_GPIO_NUM 25 #define PCLK_GPIO_NUM 19 #define LED_GPIO_NUM -1
- Fill in your Wifi
ssidandpassword. - Click on the
Uploadarrow at the top of the IDE to flash the script onto the baord. - Open the serial monitor:
- Change the
Baud Rate(top right of the serial monitor panel) to115200. - Click the
Clear Outputbutton (just above theBaud Ratedrop down. - Press the
RSTbutton on your board (bottom left). - You should see:
WiFi connecting.... WiFi connected Camera Ready! Use 'http://xxx.xxx.xxx.xxx' to connect
- Change the
- Copy the URL from the serial monitor into your browser.
- Click
Get Stillto confirm that the camera is working. - You can play with the settings. Some settings may cause your app to crash - restart and try again.
#Arduino Cloud Setup
- Sign up for Arduino Cloud.
- Skip out of the automated setup.
- Click on
Devicesin the left navigation menu. - Click on
ADD DEVICE. - Click on
Compatible device. - Select
ESP32andESP32 Wrover Module. - Name your device
smart_meter_deviceand click the check mark βοΈ. - Download and save your
Device IDandSecret Key. - Click on
Thingsin the left navigation menu. - Click
Create Thingβ. - Rename thing to
smart_meter_thing. - Click
AddCoud variable:- Name:
powerW. - Type:
Power. - Variable Permission:
Read Only. - Variable Update Policy:
On Change.
- Name:
- Click
AddCoud variable:- Name:
consumptionWh. - Type:
Floating Point Number. - Variable Permission:
Read Only. - Variable Update Policy:
On Change.
- Name:
#Arduino Cloud connection
-
In the Arduino IDE, click on the
Library Managerin the left navigation panel and search for and installArduinoIoTCloudwith all dependancies. -
Make a new sketch (
File/New Sketch) and save it on your desktop with the namesmart_meter. This will create a new folder on your desktop calledsmart_meterand inside that will be a filesmart_meter.ino. -
Make sure you have file extensions visible in explorer/finder.
-
Create a copy of
smart_meter.inoand rename it tothingProperties.h. Make SURE the extension has changed. This file will not be visible in the Arduino IDE. -
Replace the contents of each file with the files below:
smart_meter.ino#include "thingProperties.h" void setup() { Serial.begin(9600); delay(1500); initProperties(); ArduinoCloud.begin(ArduinoIoTPreferredConnection); setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); } int i = 0; void loop() { ArduinoCloud.update(); delay(1000); powerW = (i==0 ? i++: i--); }
thingProperties.h#include <ArduinoIoTCloud.h> #include <Arduino_ConnectionHandler.h> const char DEVICE_LOGIN_NAME[] = "*********"; // Copy from downloaded PDF const char DEVICE_KEY[] = "****************"; // Copy from downloaded PDF const char SSID[] = "*********"; // Network SSID (name) const char PASS[] = "*********"; // Network password (use for WPA, or use as key for WEP) float consumptionWh; CloudPower powerW; void initProperties(){ ArduinoCloud.setBoardId(DEVICE_LOGIN_NAME); ArduinoCloud.setSecretDeviceKey(DEVICE_KEY); ArduinoCloud.addProperty(consumptionWh, READ, ON_CHANGE, NULL); ArduinoCloud.addProperty(powerW, READ, ON_CHANGE, NULL); } WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);
- Click on
Dashboardsin the left navigation menu. - Click on
Create dashboardβ. - Rename the dashboard to
Smart Meter Dashboard. - Click
EditβοΈ at the top right. - Click
Add. - Select
Chart. - Click
Link Variable, selectpowerWand clickLINK VARIABLEand thenDONE. - You should see the current bounce between 0 and 1 π.
- Replace the code in
smart_meter.inowith the code below. - Comment out the appropriate camera pins.
- Have fun adding to your dashboard and testing your code!
- Work on extra features like:
- Showing the data on the display.
- Implementing flash memory so your data is saved if you loose power.
- Auto reset the consumption on the 1st of the month - or add monthly consumption.
- Improve power consumption to keep your meter running all month!
// Copyright (c) 2025 BMEC Technologies. All rights reserved.
// Source code for IoT smart meter for prepaid meters.
// See https://github.com/BMEC/BME045-1 for details.
#include "thingProperties.h"
#include "esp_camera.h"
// If your device has a mic.
// Pins as per the board silk - note the diagram is incorrect.
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 4
#define SIOD_GPIO_NUM 18
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 36
#define Y8_GPIO_NUM 15
#define Y7_GPIO_NUM 12
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 35
#define Y4_GPIO_NUM 14
#define Y3_GPIO_NUM 13
#define Y2_GPIO_NUM 34
#define VSYNC_GPIO_NUM 5
#define HREF_GPIO_NUM 27
#define PCLK_GPIO_NUM 25
#define LED_GPIO_NUM -1
// If your device has no mic.
// Pins as per the diagram.
#define PWDN_GPIO_NUM 26
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 32
#define SIOD_GPIO_NUM 13
#define SIOC_GPIO_NUM 12
#define Y9_GPIO_NUM 39
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 23
#define Y6_GPIO_NUM 18
#define Y5_GPIO_NUM 15
#define Y4_GPIO_NUM 4
#define Y3_GPIO_NUM 14
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 27
#define HREF_GPIO_NUM 25
#define PCLK_GPIO_NUM 19
#define LED_GPIO_NUM -1
// FEATURE implement flash to store usage.
// FEATURE reset usage on first of month.
// Change in average pixel brighness required to trigger a flash.
static const int32_t flashThreshold = 50;
// Watts per hour per meter flash.
static const float wattHourPerFlash = 1.0;
// Average pixel brightness of the previous frame.
int32_t previousBrightnessAverage = 0;
// Time since last flash in micro-seconds since boot.
int64_t previousFlashTimeUs = 0;
/// This task initialises the camera and then runs the camera and smart meter loop.
void meterTask() {
// Camera config.
camera_config_t cameraConfig;
cameraConfig.pin_d0 = Y2_GPIO_NUM;
cameraConfig.pin_d1 = Y3_GPIO_NUM;
cameraConfig.pin_d2 = Y4_GPIO_NUM;
cameraConfig.pin_d3 = Y5_GPIO_NUM;
cameraConfig.pin_d4 = Y6_GPIO_NUM;
cameraConfig.pin_d5 = Y7_GPIO_NUM;
cameraConfig.pin_d6 = Y8_GPIO_NUM;
cameraConfig.pin_d7 = Y9_GPIO_NUM;
cameraConfig.pin_xclk = XCLK_GPIO_NUM;
cameraConfig.pin_pclk = PCLK_GPIO_NUM;
cameraConfig.pin_vsync = VSYNC_GPIO_NUM;
cameraConfig.pin_href = HREF_GPIO_NUM;
cameraConfig.pin_sccb_sda = SIOD_GPIO_NUM;
cameraConfig.pin_sccb_scl = SIOC_GPIO_NUM;
cameraConfig.pin_pwdn = PWDN_GPIO_NUM;
cameraConfig.pin_reset = RESET_GPIO_NUM;
cameraConfig.xclk_freq_hz = 20000000;
cameraConfig.pixel_format = PIXFORMAT_GRAYSCALE;
cameraConfig.fb_location = CAMERA_FB_IN_PSRAM;
cameraConfig.frame_size = FRAMESIZE_QVGA;
cameraConfig.grab_mode = CAMERA_GRAB_LATEST;
cameraConfig.fb_count = 1;
// Camera init.
esp_err_t err = esp_camera_init(&cameraConfig);
if (err != ESP_OK) {
log_e("Camera init failed with error: 0x%x - %s",
err,
esp_err_to_name(err));
log_e("Retry in 2 seconds...");
delay(2000);
ESP.restart();
}
// Loop indefinitely.
while (true) {
// Delay 50 ms to give other tasks a chance.
delay(50);
// Instantiate the frame buffer.
camera_fb_t* frameBuffer;
// Read image from camera into buffer.
frameBuffer = esp_camera_fb_get();
// Check for valid frame and skip to next loop if failed.
if (!frameBuffer) {
log_w("webSocketTask: Camera capture failed");
return;
}
int32_t brightnessAverage = 0;
uint32_t pixelIndex = -1;
// Sum the value of all pixels.
while (++pixelIndex < frameBuffer->len) {
brightnessAverage += ((uint8_t*)frameBuffer->buf)[pixelIndex];
}
// Compute the average.
brightnessAverage /= pixelIndex;
// Release the frame buffer.
esp_camera_fb_return(frameBuffer);
// Debugging log to see the brightness in every loop.
log_v("brightnessAverage %ld", brightnessAverage);
// Detetect flash.
if ((brightnessAverage - previousBrightnessAverage) > flashThreshold) {
// Add the usage to the consumption.
consumptionWh += wattHourPerFlash;
// Get the flash time.
int64_t flashTimeUs = esp_timer_get_time();
// Power in watts is watthoursPerflash devided by the time since the last flash.
// The time delta needs to be converted from micro-seconds to hours.
powerW = wattHourPerFlash / (((float)(flashTimeUs - previousFlashTimeUs)) / 1000.0 / 1000.0 / 60.0 / 60.0);
// Update the previous flash time.
previousFlashTimeUs = flashTimeUs;
log_i("**Flash detected**\nconsumptionWh, %.2f \tpowerW, %.2f", consumptionWh, (float)powerW);
}
// Update the previous brightness.
previousBrightnessAverage = brightnessAverage;
}
}
void setup() {
// Enable Serial.
Serial.begin(9600);
// Arduino Cloud init.
initProperties();
// Start Arduino Cloud.
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
// Set Arduino cloud log level.
setDebugMessageLevel(2);
// Print Arduino Cloud debug info.
ArduinoCloud.printDebugInfo();
// Create and start the meterTask.
(void)xTaskCreatePinnedToCore([](void* nullRef) {
meterTask();
},
"meterTask", 5000, nullptr, 6, nullptr, 1);
}
void loop() {
// Service Arduino Cloud.
ArduinoCloud.update();
// Delay 100 ms to give other tasks a chance.
delay(100);
}