Energy Monitor, Using API Calls on a ESP32 (WT32-SC01)

by LV2243 in Circuits > Arduino

2858 Views, 2 Favorites, 0 Comments

Energy Monitor, Using API Calls on a ESP32 (WT32-SC01)

PXL_20230722_090411421~2.jpg

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

WT32-SC01.png

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

Squareline settings.png
wt32-sc01 Squareline settings file.png

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

Squareline UI.png

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

PlatformIO new project.png
Project Wizard.png
Visual studio code libraries.png

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

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

LVGL config.png

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

Squareline export files.png
Visual studio files including ui export.png

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

built.png
Flash 97 percent.png

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.