Energy Monitor, Using API Calls on a ESP32 (WT32-SC01)
by LV2243 in Circuits > Arduino
3585 Views, 3 Favorites, 0 Comments
Energy Monitor, Using API Calls on a ESP32 (WT32-SC01)
I purchased a WT32-SC01. This is a ESP32 development board with a touchscreen attached. It has onboard WIFI and Bluetooth. This is a very nice and compact designed board. Much easer to use then a separate board and screen.
In my fuse box I have a smart meter which allows me to monitor my gas and electricity usage trough my smartphone or a website. I didn't always want to open my app and wanted to see the usage live, I decided to buy this device and see if I could hook it up to my smart meter.
This instructible can also be used if you just want to know how to make a design for the screen using a drag and drop UI designer, or how to make API calls, or if you just want to know how to get the thing working.
Note, if you already own the WT32-SC01, that's fine. But I recommend don't buy one, buy the WT32-SC01 plus.
The plus has lots more memory and some other improvements.
Supplies
What you need:
- WT32-SC01 (about €35)
- Visual studio code (free)
- Some libraries (free)
- Squareline Studio (free trial)
- Cemm basic (https://webshop.cedel.nl/CEMM-basic)
- 3D printer (optional)
Getting the Software
Presuming you already have the WT32-SC01 (hopefully the plus version). It's now down to the software.
Included with the device, was a card, stating that we should use the 8ms software (http://8ms.xyz).
This is online drag and drop software for easy UI development.
For me this was complete unusable do to it being in a chinese language and poor documentation. Did my best but could not get it to work.
After some google searches I found an other website which looked even better: https://squareline.io/
This one comes in english and has a trial version.
Before we are gone use this software we need to install some other software before. This is explained is step 2.
Visual Studio Code
To write the code for de ESP32 development board I use Visual studio code
follow the instructions on https://randomnerdtutorials.com/vs-code-platformio-ide-esp32-esp8266-arduino/
This installs:
- Visual studio code
- PlatformIO
- Python
Configure the Squareline Studio Software
Go to https://squareline.io/ and download the software.
Pay attention to where the software is installed, you will need this location later.
First thing to do is make a new project (pick a location outside the installation location, like documents or your desktop)
Then go to File, Project Settings.
Here you see the settings for your display.
When you start you don't see the WT32-SC01 option in Board properties.
he are gone fix this now.
- Close SquareLine Studio
- Go to the installation folder (mine was C:\Program Files\SquareLine Studio 1.3.1)
- Go to the boards folder
- Make a directory espressif (if not already there)
- Make a folder in the espressif folder, called wt32_sc01_plus
So you have the folders: C:\Program Files\SquareLine Studio 1.3.1\boards\espressif\wt32_sc01_plus
(replace C:\Program Files\SquareLine Studio 1.3.1\ with whatever installation folder you have)
Open notepad or other text editor you have and make a text file with the following content:
{
"version": "1.0.0",
"group": "Espressif",
"title": "WT32-SC01 Plus",
"keywords": "Espressif, ESP-BOX, ESP32S3, ESP WROVER KIT, ESP32",
"width": "320",
"height": "480",
"width_min": "320",
"height_min": "480",
"width_max": "320",
"height_max": "480",
"offset_x": 0,
"offset_y": 0,
"rotation": 0,
"color_depth": "8, 16, 16 sw, 32",
"lvgl_export_path": "",
"lvgl_include_path": "lvgl.h",
"supported_lvgl_version": "8.2.0",
"pattern_match_files": "./CMakeLists.txt",
"language": "C",
"ui_export_path": "./main/ui",
"url": "https://github.com/espressif/esp-bsp",
"short_description": "Wireless Tag WT32-SC01 Plus",
"long_description": "WT32-SC01 Plus is an ESP32-based development board produced by Wireless Tag."
}
Save this file in the folder you previously made and name it: wt32_sc01_plus.slb
This tells Squareline studio what the display looks like.
This file works for the wt32-sc01 and the plus variant
Open Squareline Studio and choose the newly created board.
Set all other setting like the screenshot. (use your own file locations)
Designing the UI for My Application
Above is the finished design I made. I stared with something completely different witch used a lot of images. This highlighted the restriction of the WT32-SC01. Lack of memory. When you use different fonts and pictures the flash memory of the WT32-SC01 is insufficient. (This is why I order the plus version as well, it has lots more flash memory)
So I changed the UI and use only two small images, the electricity sign and the gas sign (both are 50 x 50 pixels)
On the left you can see the other items I used.
- Screen
- Panel
- Arc
- panel
- Arc
- Panel
- Label
- Label
- Image
- Label
- Label
- Image
- Assets
- Image (use png with transparent background)
- Image
- font
- Code for the font (generated with the built-in font manager)
Note that the order of the widgets is important (one on top of the other)
The widgets I will be controlling from within visual studio I have given a nice name.
You can use events, but I did not. Doing everything in code.
- GasArc
- PowerArc
- ActualPowerLabel
- ActualGasLabel
The nice colors and background I made using this tutorial: https://www.youtube.com/watch?v=6uKf5Bj0xcc&ab_channel=SquareLine
For use of the pictures you drag them from you pc to the Assets tab
For use of fonts
- Go to your your fonts folder (C:\Windows\Fonts)
- Copy the font you want to use to the assets tab
- Go to the Font Manager (upper right corner)
- choose the font from the assets folder
- choose a size
- choose a name
- click create
- Now you can use this font
Note: Images and fonts use a lot of flash memory!
When done designing you UI
- Click Export
- Export UI Files
- Remember your export location. (this was set in the project settings)
Visual Studio Code
If you have not done already, go to visual studio code and create a new project using PlatformIO
- Enter a name
- Board: Espressif ESP32 Dev Module
- Framework: Arduino
In order all this to work we need some libraries
- LovyanGFX (the display driver)
- LVGL (Graphics Library)
- vt arduino tools
You can find and install those libraries using the Libraries tab in PIO Home
- LovianGFX
- enter GFX in the search box and scroll down till you see LovyanGFX by lovyan03
- choose add to project
- LVGL
- enter LVGL in the search box and scroll down till you see LGVL by LVGL
- choose add to project
- vt arduino tools
- enter vt arduino tools in the search box and scroll down till you see vt-arduino-tools by Vivatsathorn Thitasirivit
- choose add to project
Configure LVGL
We have to configure the LVGL library
This is done by changing some settings in the lv_conf.h file
- #define LV_COLOR_DEPTH 16
- #define LV_COLOR_16_SWAP 0
Don't use 32 bit, this is not supported by LVGL
That's why we use 16 bit in Squareline Studio and LGVL
Copy the Files From Squareline Studio to Your Project
We are now ready to copy the files from the Squareline Studio project to our Visual studio project
- Open file explore to your Squareline Export files
- In visual studio, right click the main.cpp file and click "reveal in file explorer"
- Copy the Squareline export files to the visual studio folder so it looks like the second image
The files are shown automatically in Visual studio.
Change the Main.cpp Code
Now we can write the code in visual studio main.cpp
click on main.cpp
write the following code:
#define LGFX_AUTODETECT // Autodetect board
#define LGFX_USE_V1 // set to use new version of library
#include <LovyanGFX.hpp> // main library
static LGFX lcd; // declare display variable
#include <lvgl.h>
#include "lv_conf.h"
#include "ui.h"
#include "WiFi.h"
#include <HTTPClient.h>
/*** Setup screen resolution for LVGL ***/
static const uint16_t screenWidth = 480;
static const uint16_t screenHeight = 320;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * 10];
// Variables for touch x,y
#ifdef DRAW_ON_SCREEN
static int32_t x, y;
#endif
/*** Function declaration ***/
void display_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
void touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data);
//only for my project
unsigned long LastTimeApiCall = 0;
unsigned long timerDelayApiCall = 30000;
unsigned long LastTimeNoWifi = 0;
unsigned long timerDelayNoWifi = 60000L * 15L;
bool WifiConnected = false;
const char* ssid = "use your own ssid name";
const char* password = "use your own wifi password";
const char* ApiPathGas = "http://cemm.home/open-api/v1/p1-gas/data/day/";
const char* ApiPathPower = "http://cemm.home/open-api/v1/p1/realtime/";
//my wifi truns off between 01:00 and 07:00
//This causes the device to lose connection
//When this happens the device restarts every 5 minutes to try and regain connection
//this events gets automatically called when losing the Wifi connection
void Wifi_disconnected(WiFiEvent_t event, WiFiEventInfo_t info)
{
LastTimeNoWifi = millis();
WifiConnected = false;
}
//this events gets automatically called on Wifi connection
void Wifi_Connected(WiFiEvent_t event, WiFiEventInfo_t info)
{
Serial.println("Connected to WIFI access point");
WifiConnected = true;
}
//start the Wifi connectino
void StartWifi()
{
LastTimeNoWifi = millis();
WiFi.onEvent(Wifi_disconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
WiFi.onEvent(Wifi_Connected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED);
WiFi.begin(ssid, password);
Serial.println("Connecting");
byte NumberOfTries = 0;
while (NumberOfTries<20 && WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
NumberOfTries++;
}
}
void setup(void)
{
Serial.begin(115200); /* prepare for possible serial debug */
lcd.init(); // Initialize LovyanGFX
lv_init(); // Initialize lvgl
// Setting display to landscape
if (lcd.width() < lcd.height())
lcd.setRotation(lcd.getRotation() ^ 1);
/* LVGL : Setting up buffer to use for display */
lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 10);
/*** LVGL : Setup & Initialize the display device driver ***/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = display_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
I don't use the touchpanel, going to do that with the plus version
/*** LVGL : Setup & Initialize the input device driver ***/
// static lv_indev_drv_t indev_drv;
// lv_indev_drv_init(&indev_drv);
// indev_drv.type = LV_INDEV_TYPE_POINTER;
// indev_drv.read_cb = touchpad_read;
// lv_indev_drv_register(&indev_drv);
ui_init();
StartWifi();
}
//some helper
String midString(String str, String start, String finish)
{
int locStart = str.indexOf(start);
if (locStart == -1) return "";
locStart += start.length();
int locFinish = str.indexOf(finish, locStart);
if (locFinish == -1) return "";
return str.substring(locStart, locFinish);
}
//this is the api call to my smart meter, it gets the actual value of the power usage for the whole house
//it extract the value using some simple string operations
//can't use a json library, because this uses to much memory :(
double GetPowerValue()
{
if (WifiConnected)
{
HTTPClient http;
http.begin(ApiPathPower);
// Send HTTP GET request
int httpResponseCode = http.GET();
if (httpResponseCode > 0)
{
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
String json = http.getString();
Serial.println(json);
if (json.length() > 0)
{
Serial.println("Electric response.length() > 0");
String electricPower = midString(json, "electric_power", "]");
int lastIndex = electricPower.lastIndexOf(',') + 1;
if (lastIndex > 1 && lastIndex < electricPower.length())
{
http.end();
return (electricPower.substring(lastIndex)).toDouble();
}
}
http.end();
}
}
return 0.0;
}
//Same for gas, only now it's a lot of data wich I have to add up from liters per second to a use of M3 per day
//can't use a json library, because this uses to much memory :(
double GetGasValue() {
//Check WiFi connection status
if (WifiConnected) {
HTTPClient http;
String serverPath = ApiPathGas;
http.begin(serverPath.c_str());
// Send HTTP GET request
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
String json = http.getString();
Serial.println(json);
// Find the start position of the "gas" array
int gasArrayStart = json.indexOf("\"gas\":[");
if (gasArrayStart == -1) {
Serial.println("Error: 'gas' array not found");
return 0.0;
}
// Find the end position of the "gas" array
int gasArrayEnd = json.indexOf("]]", gasArrayStart);
if (gasArrayEnd == -1) {
Serial.println("Error: Invalid 'gas' array");
return 0.0;
}
gasArrayEnd += 2;
// Extract the "gas" array values
json = json.substring(gasArrayStart + 7, gasArrayEnd);
// Calculate the sum of the "gas" values
double sum = 0;
// Process individual values from the "gas" array
int valueStart = json.indexOf(',', 0);
int valueEnd = json.indexOf(']', valueStart);
while (valueEnd != -1) {
String valueString = json.substring(valueStart + 1, valueEnd);
valueString.trim();
// Convert value to int if possible and add to the sum
double value = valueString.toDouble();
Serial.print("string: ");
Serial.print(valueString);
Serial.print(" int: ");
Serial.println(value);
sum += value;
valueStart = valueEnd + 3;
valueStart = json.indexOf(',', valueStart);
valueEnd = json.indexOf(']', valueStart);
}
// Process the last value in the "gas" array
String lastValueString = json.substring(valueStart);
lastValueString.trim();
// Convert last value to int if possible and add to the sum
int lastValue = lastValueString.toInt();
sum += lastValue;
// Print the sum of the "gas" values
Serial.print("Sum of gas values: ");
Serial.println(sum);
// Free resources
http.end();
return sum/1000;
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
} else {
Serial.println("WiFi Disconnected");
}
return 0.0;
}
//the loop
//here is where the LVGL magic happens
//the calls to the screen are made trough calling the variables made in Squareline studio and setting values to them
void loop()
{
lv_timer_handler(); /* let the GUI do its work */
if (!WifiConnected && (millis() - LastTimeNoWifi) > timerDelayNoWifi)
{
Serial.println("Disconnected from WIFI access point");
//long delay
unsigned long waitTimeToRestart = 300000L;
delay(waitTimeToRestart);
ESP.restart();
}
if (WifiConnected && (millis() - LastTimeApiCall) > timerDelayApiCall)
{
//you must use char, so a conversion is needed
char outBuffer[16];
double cemmValue = GetGasValue();
dtostrf(cemmValue, 5, 2, outBuffer);
//set the label and move the arc position
//all items you used in Squareline Studio are available in Visual studio, just start by typing ui_ and
//then the name you gave it
lv_label_set_text(ui_ArctualGasLabel, outBuffer);
lv_arc_set_value(ui_GasArc,(int)((cemmValue + 0.5) *10));
cemmValue = GetPowerValue();
bool isNegative = cemmValue < 1;
dtostrf(cemmValue, 5, 0, outBuffer);
lv_label_set_text(ui_ArctualPowerLabel, outBuffer);
if (isNegative)
{
//negative means I'm producing power,
// set the arc to work from right to left and set color
// this is defined in Squareline Studio, and activated using states (LV_STATE_CHECKED)
cemmValue = cemmValue * -1;
lv_arc_set_mode(ui_PowerArc, LV_ARC_MODE_REVERSE);
if (lv_obj_get_state(ui_PowerArc)==LV_STATE_USER_1)
{
lv_obj_clear_state(ui_PowerArc, LV_STATE_USER_1);
lv_obj_add_state(ui_PowerArc, LV_STATE_CHECKED);
}
}
else
{
//positive means I'm using power,
// set the arc to work from left to right and use a red color
// this is done in Squareline Studio, using states (LV_STATE_USER_1)
lv_arc_set_mode(ui_PowerArc, LV_ARC_MODE_NORMAL);
if (lv_obj_get_state(ui_PowerArc)==LV_STATE_CHECKED)
{
lv_obj_clear_state(ui_PowerArc, LV_STATE_CHECKED);
lv_obj_add_state(ui_PowerArc, LV_STATE_USER_1);
}
}
lv_arc_set_value(ui_PowerArc,(int)(cemmValue + 0.5));
LastTimeApiCall = millis();
}
//I don't use this
#ifdef DRAW_ON_SCREEN
/*** Draw on screen with touch ***/
if (lcd.getTouch(&x, &y))
{
lcd.fillRect(x - 2, y - 2, 5, 5, TFT_RED);
lcd.setCursor(380, 0);
lcd.printf("Touch:(%03d,%03d)", x, y);
// }
#endif
}
// /*** Display callback to flush the buffer to screen ***/
void display_flush(lv_disp_drv_t * disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
lcd.startWrite();
lcd.setAddrWindow(area->x1, area->y1, w, h);
lcd.pushPixels((uint16_t *)&color_p->full, w * h, true);
lcd.endWrite();
lv_disp_flush_ready(disp);
}
Built Your Program
Now your ready to built everything
Press the built button and watch for errors.
Mine built just fine
If the built is ok, you can use the upload button to send it to your device.
As you can see, this simple program already uses 97% of flash memory
The plus version should eliminate this problem.
I designed and printed a super simple case.
If needed, let me know.
Good luck and happy coding.