logo elektroda
logo elektroda
X
logo elektroda

[BK7231N/CB2S/BL0937] Wifi Smart Plug with Energy Measurement (RMC021)

Raufaser 13755 47
Best answers

How can I calibrate the BL0937 current and power readings on this BK7231N smart plug accurately without unsafe live measurements?

Calibrate it by using interrupts to count CF and CF1 pulses instead of reading single pulses with `digitalRead()`, then adjust the coefficients against known reference loads or a trusted meter [#21273841] The recommended approach is to attach one interrupt to CF and one to CF1, increment a volatile counter in each ISR, and every second read/reset the counters so you can estimate pulses per second and tune the calibration values [#21273841] Do not use the blocking `digitalRead`/pulse-wait loop for metering, because it is imprecise and blocks program execution [#21273841] The OpenBK BL0937 driver and the ESP library linked in the thread show the same general approach and can be used as references for the calibration logic [#21273631]
Generated by the language model.
ADVERTISEMENT
  • #31 21148742
    Daro1003
    Level 34  
    Posts: 2711
    Help: 295
    Rate: 603
    I added an ESP8266 module to such a plug, onto which I threw Tasmote and then added it to domoticz.
    Of course CB2S soldered out and in its place on the ESP8266 wires

    If anyone would be interested in the topic of converting to ESP 8266 and Tasmote it downloads:

    PCB with marked pin numbers for connecting ESP8266. .


    1. VCC +3.3V
    2. GND
    3. button -> GPIO 0
    4. hlwbl SEL_i -> GPIO 12
    5. relay -> GPIO 15
    6. HLWBL CF1 -> GPIO 4
    7. BL0937 CF -> GPIO 5
    8. LED -> GPIO 2

    Works ok already 3 pieces so converted function in home automation. Switching on and off devices and measuring energy consumption all in Domoticz. Much better value than the SP111, just a little of your own time at night.

    Maybe the above list will help someone. To get there, take a meter and look along the paths.
    It will also be useful then:
    HLW8012 pin configuration diagram with function descriptions. .
  • ADVERTISEMENT
  • #32 21149147
    tun0
    Level 7  
    Posts: 12
    p.kaczmarek2 wrote:
    None of those look promising. Still, what are those 3 soic chips at the bottom of the blue one?


    Yeah, that's pretty much what I was afraid of. I haven't been able to determine any of those 3 chips, yet. Perhaps with my microscope I can, but haven't gotten around to try that yet.
    Also, pretty sure I should have a "known good" adapter "somewhere". But those things tend to be darn good at playing hide and seek 😂

    Added after 2 [minutes]:

    >>21148742

    Interesting! 🤔
    Which ESP8266 module did you use for this modification? And how did you configure Tasmota?
  • ADVERTISEMENT
  • #33 21177527
    p.kaczmarek2
    Moderator Smart Home
    Posts: 14431
    Help: 650
    Rate: 12399
    CB2S/WB2S can be replaced with TYWE2S or ESP02S.
    Diagram of the TYWE2S module with pin descriptions. WiFi module ESP-02S with markings from Doiting.
    To quote Blakader: This module can replace modules such as: BT2S, CB2S, ESP-02S, FL_M99_V1, TYWE2S, TYZS6, TW-02, WR2, WB2, WBR2, WBR2D, WR2E, WB2S, WA2, XT-BL02, ZS2S
    Helpful post? Buy me a coffee.
  • #34 21271875
    mjab

    Level 10  
    Posts: 188
    Rate: 15
    I converted a BLOW WiFi SMART SOCKET (about 25 PLN on Allegro) with Volt and Amp meter just like that. All the pins match, only for the LED instead of GPIO2 I used GPIO14 because under 2ka is the built-in LED in the ESP8266.

    I soldered out the CB2S module using braid and soldered a clean ESP8266MOD chip in its place with wires to these pins. On the ESP8266 I connected the EN pins with a 10k resistor to VCC and GPIO15 with a 10k resistor to GND. This fits snugly, no extra PCB to the ESP8266 will go in.

    I have currently uploaded SUPLA on a test basis and it works ok, ultimately I want to write my own program in the Arduino IDE.
    Company Account:
    Jabłoński KOMPUTERY
    Ogrodowa 3a, Rypin, 87-500 | Tel.: 507XXXXXX (Show) | Company Website: https://www.k.j.pl
  • #35 21272544
    mjab

    Level 10  
    Posts: 188
    Rate: 15
    I've spent a lot of time on the https://github.com/xoseperez/hlw8012 library and ... In the program under the Arduino IDE for the ESP8266 it measures some silly things. It shows Volts in thousands ... Over 3h I tried to calibrate it and only Watts was successful. The Supla, on the other hand, measures to the nearest decimal, so is it possible? I am momentarily stuck with this ...
    Company Account:
    Jabłoński KOMPUTERY
    Ogrodowa 3a, Rypin, 87-500 | Tel.: 507XXXXXX (Show) | Company Website: https://www.k.j.pl
  • ADVERTISEMENT
  • #36 21272970
    p.kaczmarek2
    Moderator Smart Home
    Posts: 14431
    Help: 650
    Rate: 12399
    Have you calibrated?

    The watts are on CF, and the current and voltage are on CF1. You need to set the state on SEL.

    Diagram of the HLW8012 integrated circuit with pin labels and functions. .
    Helpful post? Buy me a coffee.
  • #37 21273019
    mjab

    Level 10  
    Posts: 188
    Rate: 15
    All these libraries in the Arduino IDE for the BL0937 are lame !
    I started writing all the code myself....

    Currently my code is like this :

    
    #include <Arduino.h>
    #include "Timer.h"
    #include <ESP8266WiFi.h>
    #include <WiFiClient.h>
    #include <ESP8266WebServer.h>
    #include <ESP8266HTTPClient.h>
    #include <ESP8266HTTPUpdateServer.h>
    #include <ESP8266mDNS.h>
    
    Timer t;
    WiFiClient client;
    ESP8266WebServer server(80);
    ESP8266HTTPUpdateServer httpUpdater;
    
    
    // --- USTAWIENIA ---
    const String plyta = "BLOW WiFi SMART SOCKET [EAN : 5900804116486]"; // Producent i model płyty głównej urządzenia
    const char *OSver = "v.2.3"; // Wersja oprogramowania
    const char *deviceName = "jGniazdko"; // Nazwa widoczna w routerze (bramie)
    
    const char *ssid =  "xxxxxx"; // Nazwa sieci WiFi
    const char *pass =  "xxxxxxxx"; // Hasło do sieci WiFi
    
    const String DOMOTICZ_SERVER_IP = "[login]:[hasło]@192.168.x.x:8080"; // Login, hasło i adres IP wraz z portem do serwera Domoticz
    // ------
    
    
    int SerwerWWW = 1;
    
    // BL0937
    float Waty = 0.00;
    int Volty = 0;
    float Ampery = 0.00;
    
    // Deklaracja i definicja funkcji do mierzenia częstotliwości
    unsigned long measurePulse(int pin) {
      unsigned long pulseDuration = pulseIn(pin, HIGH);  // Pomiar czasu trwania impulsu w mikrosekundach
      if (pulseDuration > 0) {
        return 1000000 / pulseDuration;  // Zwraca częstotliwość w Hz (impulsy na sekundę)
      }
      return 0;
    }
    
    // Odczyt napięcia
    float readVoltage() {
      digitalWrite(12, HIGH);  // SEL = HIGH -> Odczyt napięcia
      delay(50);  // Małe opóźnienie na stabilizację sygnału
      unsigned long freqCF1 = measurePulse(4);  // CF1 -> Pin 4
      delay(50);
      freqCF1 += measurePulse(4);
      freqCF1 = freqCF1 / 2;
    
      float voltage = freqCF1 * 0.00733 * 1.137;  // Przelicznik zależny od kalibracji
      return voltage;
    }
    
    // Odczyt prądu
    float readCurrent() {
      digitalWrite(12, LOW);  // SEL = LOW -> Odczyt prądu
      delay(50);  // Małe opóźnienie na stabilizację sygnału
      unsigned long freqCF1 = measurePulse(4);  // CF1 -> Pin 4
      float cosPhi = 0.9;  // Współczynnik mocy
      float current = (freqCF1 * 0.00001405) * cosPhi;  // Kalibracja z uwzględnieniem współczynnika mocy
      return current;
    }
    
    // Odczyt mocy
    float readPower() {
      unsigned long freqCF = measurePulse(5);  // CF -> Pin 5
      float power = freqCF * 0.00306;  // Przelicznik zależny od kalibracji
      return power;
    }
    
    void miernik() {
     SerwerWWW = 0;
     delay(10);
     Ampery = readCurrent();
     Volty = readVoltage();
     Waty = readPower();
     SerwerWWW = 1;
    }
    
    
    String getValue(String data, char separator, int index) { 
     int found = 0; 
     int strIndex[] = {0, -1}; 
     int maxIndex = data.length()-1; 
         
     for(int i=0; i<=maxIndex && found<=index; i++){ 
      if(data.charAt(i)==separator || i==maxIndex){ 
       found++; 
       strIndex[0] = strIndex[1]+1; 
       strIndex[1] = (i == maxIndex) ? i+1 : i; 
      } 
     } 
         
     return found>index ? data.substring(strIndex[0], strIndex[1]) : ""; 
    }
    
    
    void www() {
     String html = "<html><head><meta charset='UTF-8'>";
     html += "<style>";
     html += "body { background-color: black; color: white; }";
     html += "table { width: 800px; background-color: white; color: black; border-spacing: 10px; }";
     html += "th { background-color: orange; }";
     html += ".color-bar { height: 32px; width: 100%; background: linear-gradient(to right, green, yellow, red); }";
     html += ".progress-bar { width: 100%; background-color: gray; }";
     html += "h1 { color: white; }";
     html += ".center-cell { text-align: center; }";
     html += ".option-column { width: 25%; }";
     html += ".value-column { width: 75%; }";
     html += ".status-active { color: darkred; }";
     html += ".status-inactive { color: darkgreen; }";
     html += "button { padding: 10px; background-color: orange; border: none; color: white; font-size: 16px; cursor: pointer; }";
     html += "</style>";
     html += "<meta http-equiv=\"refresh\" content=\"2; url=/\"></head><body>";
     html += "<center><h1>- " + String(deviceName) + " -</h1>";
     html += "<table>";
     html += "<tr><th class='option-column'>Opcja</th><th class='value-column'>Wartość</th></tr>";
     html += "<tr><td style='text-align: right;'>Płyta główna : </td><td style='text-align: left;'><b>" + String(plyta) + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'>Wersja programu : </td><td style='text-align: left;'><b>" + String(OSver) + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'> </td><td style='text-align: left;'> </td></tr>";
     html += "<tr><td style='text-align: right;'>IP : </td><td style='text-align: left;'><b>" + WiFi.localIP().toString() + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'>MAC : </td><td style='text-align: left;'><b>" + WiFi.macAddress() + "</b></td></tr>";
     int SilaWiFi = WiFi.RSSI();
     if (SilaWiFi < -65) html += "<tr><td style='text-align: right;'>Sygnał WiFi : </td><td style='text-align: left;'><b>" + String(SilaWiFi) + " dBm</b> [ SLABY SYGNAL ! ]</td></tr>";
     else html += "<tr><td style='text-align: right;'>Sygnał WiFi : </td><td style='text-align: left;'><b>" + String(SilaWiFi) + " dBm</b></td></tr>";
     html += "<tr><td style='text-align: right;'> </td><td style='text-align: left;'> </td></tr>";
     html += "<tr><td style='text-align: right;'>Volty : </td><td style='text-align: left;'><b>" + String(Volty) + " V</b></td></tr>";
     html += "<tr><td style='text-align: right;'>Ampery : </td><td style='text-align: left;'><b>" + String(Ampery,1) + " A</b> / 10 A</td></tr>";
     html += "<tr><td style='text-align: right;'>Waty : </td><td>";
    
     float wattPercentage = (Waty / 2300.0) * 100;
     if (wattPercentage > 100) wattPercentage = 100;
     else if (Waty < 1) wattPercentage = 0;
    
     html += "<div class='progress-bar'><div class='color-bar' style='width: " + String(wattPercentage) + "%;'></div></div>";
     html += "<span><center>" + String(Waty, 1) + " W <i>/ 2300 W</i>  [" + String(wattPercentage, 0) + "% ]</center></span></td></tr>";
     html += "</table><br>";
    
     html += "<table>";
     html += "<tr><th colspan='2'>Zasilanie</th></tr>";
     html += "<tr><td style='text-align: center;'>Stan zasilania : </td><td>";
    
     int StanGPIO15 = digitalRead(15);
    
     if (StanGPIO15 == HIGH) {
       html += "<b class='status-active'>[Aktywne] </b> <i> [Uwaga na możliwe wysokie napięcie !]</i></td></tr>";
     } else {
       html += "<b class='status-inactive'>[Wyłączone]</b></td></tr>";
     }
    
     html += "<tr><td colspan='2' style='text-align: center;'>";
     if (StanGPIO15 == HIGH) {
       html += "<button onclick=\"window.location.href='ustaw?zasilanie=0'\">Wyłącz</button>";
     } else {
       html += "<button onclick=\"window.location.href='ustaw?zasilanie=1'\">Włącz</button>";
     }
     html += "</td></tr>";
     html += "</table>";
     html += "<br><br><br><i>Prawa autorskie - Michał Jabłoński (www.k.j.pl) - Jabłoński KOMPUTERY</i></center></body></html>";
    
     server.send(200, "text/html", html);
    }
    
    
    void Polacz_WiFi() {
      if (WiFi.status() != WL_CONNECTED) {
       Serial.println("Laczenie z WiFi");
    
       WiFi.setOutputPower(20.5);
       WiFi.begin(ssid, pass);
       WiFi.hostname(deviceName);
      
       while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        digitalWrite(14, LOW);
        delay(100);
        digitalWrite(14, HIGH);
        delay(100);
       }
       
       Serial.println("");
       Serial.println("Przydzielono adres IP: ");
       Serial.print(WiFi.localIP());
       Serial.println("");
       digitalWrite(12, LOW);
      }
    }
    
    
    // Wysyłanie danych do Domoticz
    void domoticz(int idx, String nvalue, String svalue) {
      Serial.println(" - Wysylam dane do domoticz...");
      HTTPClient http;
      String adres = "http://" + DOMOTICZ_SERVER_IP + "/json.htm?type=command&param=udevice&idx=" + String(idx) + "&nvalue=" + nvalue + "&svalue=" + svalue;
      Serial.print("     URL : "); Serial.println(adres);
      http.begin(client, adres);
      http.addHeader("Content-Type", "application/html");
      int httpCode = http.GET();
      http.end();
      Serial.println("    Dane do Domoticz zostaly wyslane.");
    }
    
    
    void setup() {
     delay(1000);
     Serial.begin(115200);
    
     pinMode(0, INPUT_PULLUP); // Przycisk (LOW = Wciśnięty)
     pinMode(4, INPUT_PULLUP); // CF1
     pinMode(5, INPUT_PULLUP); // CF
     pinMode(12, OUTPUT); // SELi  [ LOW = Ampery | HIGH = Volty ]
     pinMode(14, OUTPUT); // LED niebieska
     pinMode(15, OUTPUT); // Przekaźnik
    
    
     digitalWrite(12, LOW);
     digitalWrite(14, HIGH);
     digitalWrite(15, HIGH);
    
     Polacz_WiFi();
     delay(250);
    
     httpUpdater.setup(&server);
     server.on("/", www);
     server.on("/ustaw", HTTP_GET, www);
     server.begin();
    
     miernik();
     t.every(5000, miernik);
    }
    
    
    void loop() {
     if (WiFi.status() == WL_CONNECTED and SerwerWWW == 1) server.handleClient();
     else if (WiFi.status() != WL_CONNECTED) Polacz_WiFi();
     t.update();
    }
    
    .


    However, it measures inaccurately all the time ... what needs to be improved here and how would the measurements be as accurate as in Supla?

    Also the relay control is not written yet, but this is a formality.
    Company Account:
    Jabłoński KOMPUTERY
    Ogrodowa 3a, Rypin, 87-500 | Tel.: 507XXXXXX (Show) | Company Website: https://www.k.j.pl
  • #38 21273631
    p.kaczmarek2
    Moderator Smart Home
    Posts: 14431
    Help: 650
    Rate: 12399
    Instead of measuring one pulse, try setting up an interrupt and counting how many pulses there are per second, for example, and then calibrating.

    In my case it looks like this:
    https://github.com/openshwprojects/OpenBK7231T_App/blob/main/src/driver/drv_bl0937.c

    Here is some library under ESP:
    https://github.com/MacWyznawca/HLW8012_BL0937_ESP
    Helpful post? Buy me a coffee.
  • #39 21273735
    mjab

    Level 10  
    Posts: 188
    Rate: 15
    OK, I'll try to count these pulses :)

    and this library I have already seen, but there are no examples in it and I don't know how to use it.
    Company Account:
    Jabłoński KOMPUTERY
    Ogrodowa 3a, Rypin, 87-500 | Tel.: 507XXXXXX (Show) | Company Website: https://www.k.j.pl
  • #40 21273742
    p.kaczmarek2
    Moderator Smart Home
    Posts: 14431
    Help: 650
    Rate: 12399
    See here:
    https://github.com/MacWyznawca/HLW8012_BL0937_ESP/blob/master/HLW8012_ESP82.h
    You probably init first, then calibrate HLW8012_expectedCurrent etc, and then fetch HLW8012_getCurrent
    Interestingly, as far as I can see, you don't need to call HLW8012_toggleMode as it is called automatically.
    Helpful post? Buy me a coffee.
  • #41 21273750
    mjab

    Level 10  
    Posts: 188
    Rate: 15
    I have come up with this code and am currently testing :

    
    unsigned long countPulses(int pin, unsigned long durationMs) {
      unsigned long startTime = millis();
      unsigned long pulseCount = 0;
    
      while (millis() - startTime < durationMs) {
        if (digitalRead(pin) == HIGH) {
          pulseCount++;
          while (digitalRead(pin) == HIGH);  // Czekaj na spadek sygnału
      }
    
      return pulseCount;
    }
    
    float readVoltage() {
      digitalWrite(12, HIGH);  // SEL = HIGH -> Odczyt napięcia
      unsigned long pulseCount = countPulses(4, 250);  // Zlicz impulsy na CF1 przez 250 ms
    
      float voltage = (pulseCount * 4) * 0.00733 * 1.137;  // Przelicz liczba impulsów na napięcie
      return voltage;
    }
    
    float readCurrent() {
      digitalWrite(12, LOW);  // SEL = LOW -> Odczyt prądu
      unsigned long pulseCount = countPulses(4, 250);  // Zlicz impulsy na CF1 przez 250 ms
      float cosPhi = 0.9;  // Współczynnik mocy
      float current = (pulseCount * 4 * 0.00001405) * cosPhi;  // Kalibracja prądu
      return current;
    }
    
    float readPower() {
      unsigned long pulseCount = countPulses(5, 250);  // Zlicz impulsy na CF przez 250 ms
      float power = (pulseCount * 4 * 0.00306);  // Kalibracja mocy
      return power;
    }
    
    .

    Added after 17 [minutes]:

    After setting a factor of 0.1217 for float readVoltage(), this seems to be much more stable in measurements ...

    Added after 43 [minutes]:

    Currently my code is :

    
    #include <Arduino.h>
    #include "Timer.h"
    #include <ESP8266WiFi.h>
    #include <WiFiClient.h>
    #include <ESP8266WebServer.h>
    #include <ESP8266HTTPClient.h>
    #include <ESP8266HTTPUpdateServer.h>
    #include <ESP8266mDNS.h>
    
    Timer t;
    WiFiClient client;
    ESP8266WebServer server(80);
    ESP8266HTTPUpdateServer httpUpdater;
    
    
    // --- USTAWIENIA ---
    const String plyta = "BLOW WiFi SMART SOCKET [EAN : 5900804116486]"; // Producent i model płyty głównej urządzenia
    const char *OSver = "v.2.4"; // Wersja oprogramowania
    const char *deviceName = "jGniazdko"; // Nazwa widoczna w routerze (bramie)
    
    const char *ssid =  "xxxxxx"; // Nazwa sieci WiFi
    const char *pass =  "xxxxxxxx"; // Hasło do sieci WiFi
    
    const String DOMOTICZ_SERVER_IP = "[login]:[hasło]@192.168.x.x:8080"; // Login, hasło i adres IP wraz z portem do serwera Domoticz
    // ------
    
    
    int SerwerWWW = 1;
    
    // BL0937
    float Waty = 0.00;
    float Volty = 0.00;
    float Ampery = 0.00;
    
    // Deklaracja i definicja funkcji do mierzenia ilości impulsów w zadanym czasie
    unsigned long countPulses(int pin, unsigned long durationMs) {
      unsigned long startTime = millis();
      unsigned long pulseCount = 0;
    
      while (millis() - startTime < durationMs) {
        if (digitalRead(pin) == HIGH) {
          pulseCount++;
          while (digitalRead(pin) == HIGH);  // Czekaj na spadek sygnału
        }
      }
    
      return pulseCount;
    }
    
    // Odczyt napięcia (V)
    float readVoltage() {
      digitalWrite(12, HIGH);  // SEL = HIGH -> Odczyt napięcia
      unsigned long pulseCount = countPulses(4, 250);  // Zlicz impulsy na CF1 przez 250 ms
    
      float voltage = (pulseCount * 4) * 0.1218; // Współczynnik kalibracji
      return voltage;
    }
    
    // Odczyt prądu (A)
    float readCurrent() {
      digitalWrite(12, LOW);  // SEL = LOW -> Odczyt prądu
      unsigned long pulseCount = countPulses(4, 250);  // Zlicz impulsy na CF1 przez 250 ms  
      float current = pulseCount * 0.0454; // Współczynnik kalibracji
      return current;
    }
    
    // Odczyt mocy (W)
    float readPower() {
      unsigned long pulseCount = countPulses(5, 250);  // Zlicz impulsy na CF przez 250 ms
      float power = pulseCount * 5.43; // Współczynnik kalibracji
      return power;
    }
    
    void miernik() {
     SerwerWWW = 0;
     delay(10);
     Ampery = readCurrent();
     Volty = readVoltage();
     Waty = readPower();
     SerwerWWW = 1;
    }
    
    
    String getValue(String data, char separator, int index) { 
     int found = 0; 
     int strIndex[] = {0, -1}; 
     int maxIndex = data.length()-1; 
         
     for(int i=0; i<=maxIndex && found<=index; i++){ 
      if(data.charAt(i)==separator || i==maxIndex){ 
       found++; 
       strIndex[0] = strIndex[1]+1; 
       strIndex[1] = (i == maxIndex) ? i+1 : i; 
      } 
     } 
         
     return found>index ? data.substring(strIndex[0], strIndex[1]) : ""; 
    }
    
    
    void www() {
     String html = "<html><head><meta charset='UTF-8'>";
     html += "<style>";
     html += "body { background-color: black; color: white; }";
     html += "table { width: 800px; background-color: white; color: black; border-spacing: 10px; }";
     html += "th { background-color: orange; }";
     html += ".color-bar { height: 32px; width: 100%; background: linear-gradient(to right, green, yellow, red); }";
     html += ".progress-bar { width: 100%; background-color: gray; }";
     html += "h1 { color: white; }";
     html += ".center-cell { text-align: center; }";
     html += ".option-column { width: 25%; }";
     html += ".value-column { width: 75%; }";
     html += ".status-active { color: darkred; }";
     html += ".status-inactive { color: darkgreen; }";
     html += "button { padding: 10px; background-color: orange; border: none; color: white; font-size: 16px; cursor: pointer; }";
     html += "</style>";
     html += "<meta http-equiv=\"refresh\" content=\"2; url=/\"></head><body>";
     html += "<center><h1>- " + String(deviceName) + " -</h1>";
     html += "<table>";
     html += "<tr><th class='option-column'>Opcja</th><th class='value-column'>Wartość</th></tr>";
     html += "<tr><td style='text-align: right;'>Płyta główna : </td><td style='text-align: left;'><b>" + String(plyta) + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'>Wersja programu : </td><td style='text-align: left;'><b>" + String(OSver) + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'> </td><td style='text-align: left;'> </td></tr>";
     html += "<tr><td style='text-align: right;'>IP : </td><td style='text-align: left;'><b>" + WiFi.localIP().toString() + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'>MAC : </td><td style='text-align: left;'><b>" + WiFi.macAddress() + "</b></td></tr>";
     int SilaWiFi = WiFi.RSSI();
     if (SilaWiFi < -65) html += "<tr><td style='text-align: right;'>Sygnał WiFi : </td><td style='text-align: left;'><b>" + String(SilaWiFi) + " dBm</b> [ SLABY SYGNAL ! ]</td></tr>";
     else html += "<tr><td style='text-align: right;'>Sygnał WiFi : </td><td style='text-align: left;'><b>" + String(SilaWiFi) + " dBm</b></td></tr>";
     html += "<tr><td style='text-align: right;'> </td><td style='text-align: left;'> </td></tr>";
     html += "<tr><td style='text-align: right;'>Volty : </td><td style='text-align: left;'><b>" + String(Volty,1) + " V</b></td></tr>";
     html += "<tr><td style='text-align: right;'>Ampery : </td><td style='text-align: left;'><b>" + String(Ampery,1) + " A</b> / 10 A</td></tr>";
     html += "<tr><td style='text-align: right;'>Waty : </td><td>";
    
     float wattPercentage = (Waty / 2300.0) * 100;
     if (wattPercentage > 100) wattPercentage = 100;
     else if (Waty < 1) wattPercentage = 0;
    
     html += "<div class='progress-bar'><div class='color-bar' style='width: " + String(wattPercentage) + "%;'></div></div>";
     html += "<span><center>" + String(Waty, 1) + " W <i>/ 2300 W</i>  [" + String(wattPercentage, 0) + "% ]</center></span></td></tr>";
     html += "</table><br>";
    
     html += "<table>";
     html += "<tr><th colspan='2'>Zasilanie</th></tr>";
     html += "<tr><td style='text-align: center;'>Stan zasilania : </td><td>";
    
     int StanGPIO15 = digitalRead(15);
    
     if (StanGPIO15 == HIGH) {
       html += "<b class='status-active'>[Aktywne] </b> <i> [Uwaga na możliwe wysokie napięcie !]</i></td></tr>";
     } else {
       html += "<b class='status-inactive'>[Wyłączone]</b></td></tr>";
     }
    
     html += "<tr><td colspan='2' style='text-align: center;'>";
     if (StanGPIO15 == HIGH) {
       html += "<button onclick=\"window.location.href='ustaw?zasilanie=0'\">Wyłącz</button>";
     } else {
       html += "<button onclick=\"window.location.href='ustaw?zasilanie=1'\">Włącz</button>";
     }
     html += "</td></tr>";
     html += "</table>";
     html += "<br><br><br><i>Prawa autorskie - Michał Jabłoński (www.k.j.pl) - Jabłoński KOMPUTERY</i></center></body></html>";
    
     server.send(200, "text/html", html);
    }
    
    
    void Polacz_WiFi() {
      if (WiFi.status() != WL_CONNECTED) {
       Serial.println("Laczenie z WiFi");
    
       WiFi.setOutputPower(20.5);
       WiFi.begin(ssid, pass);
       WiFi.hostname(deviceName);
      
       while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        digitalWrite(14, LOW);
        delay(100);
        digitalWrite(14, HIGH);
        delay(100);
       }
       
       Serial.println("");
       Serial.println("Przydzielono adres IP: ");
       Serial.print(WiFi.localIP());
       Serial.println("");
       digitalWrite(12, LOW);
      }
    }
    
    
    // Wysyłanie danych do Domoticz
    void domoticz(int idx, String nvalue, String svalue) {
      Serial.println(" - Wysylam dane do domoticz...");
      HTTPClient http;
      String adres = "http://" + DOMOTICZ_SERVER_IP + "/json.htm?type=command&param=udevice&idx=" + String(idx) + "&nvalue=" + nvalue + "&svalue=" + svalue;
      Serial.print("     URL : "); Serial.println(adres);
      http.begin(client, adres);
      http.addHeader("Content-Type", "application/html");
      int httpCode = http.GET();
      http.end();
      Serial.println("    Dane do Domoticz zostaly wyslane.");
    }
    
    
    void setup() {
     delay(1000);
     Serial.begin(115200);
    
     pinMode(0, INPUT_PULLUP); // Przycisk (LOW = Wciśnięty)
     pinMode(4, INPUT_PULLUP); // CF1
     pinMode(5, INPUT_PULLUP); // CF
     pinMode(12, OUTPUT); // SELi  [ LOW = Ampery | HIGH = Volty ]
     pinMode(14, OUTPUT); // LED niebieska
     pinMode(15, OUTPUT); // Przekaźnik
    
    
     digitalWrite(12, LOW);
     digitalWrite(14, HIGH);
     digitalWrite(15, HIGH);
    
     Polacz_WiFi();
     delay(250);
    
     httpUpdater.setup(&server);
     server.on("/", www);
     server.on("/ustaw", HTTP_GET, www);
     server.begin();
    
     miernik();
     t.every(5000, miernik);
    }
    
    
    void loop() {
     if (WiFi.status() == WL_CONNECTED and SerwerWWW == 1) server.handleClient();
     else if (WiFi.status() != WL_CONNECTED) Polacz_WiFi();
     t.update();
    }
    


    Still no button and relay support ... while it seems to count more precisely. The thought is to use interrupts
    attachInterrupt(digitalPinToInterrupt(4), onPulseCF1, RISING);  // Przerwanie na CF1 (pin 4)
      attachInterrupt(digitalPinToInterrupt(5), onPulseCF, RISING);   // Przerwanie na CF (pin 5)
    eventually ...
    Company Account:
    Jabłoński KOMPUTERY
    Ogrodowa 3a, Rypin, 87-500 | Tel.: 507XXXXXX (Show) | Company Website: https://www.k.j.pl
  • ADVERTISEMENT
  • #42 21273841
    p.kaczmarek2
    Moderator Smart Home
    Posts: 14431
    Help: 650
    Rate: 12399
    Do not use digitalRead, this approach is bad, imprecise and blocks program execution. It is definitely better to use an interrupt.
    See here:
    https://github.com/MacWyznawca/HLW8012_BL0937_ESP/blob/master/HLW8012_ESP82.c

    Do this, for now without entering SEL:
    1. set the GPIO interrupt once to CF and CF1
    2. in the interrupts just increment the number of pulses by 1
    3. every second or so, view how many pulses it has counted, reset the counter, make a time correction to e.g. estimate the number of pulses per second, and then calibrate it
    Helpful post? Buy me a coffee.
  • #43 21275404
    clanking6535
    Level 1  
    Posts: 1
    I obtained the same CB2S smart plug as OP. Initially I had problems with flashing, but after I removed the module and set the reply delay to 5 and everything worked smooth as butter. I can post the JSON and backup but they match OP:

    
    {
    	"sel_pin_pin":"24",
    	"rl1_lv":"1",
    	"bt1_pin":"10",
    	"net_trig":"4",
    	"jv":"1.0.5",
    	"netled1_lv":"0",
    	"netled_reuse":"0",
    	"bt1_type":"0",
    	"ffc_select":"0",
    	"nety_led":"1",
    	"vi_pin":"6",
    	"resistor":"1",
    	"over_cur":"21000",
    	"bt1_lv":"0",
    	"reset_t":"5",
    	"netled1_pin":"8",
    	"chip_type":"0",
    	"lose_vol":"90",
    	"over_vol":"265",
    	"module":"CB2S",
    	"ele_pin":"7",
    	"ch_cddpid1":"9",
    	"ch1_stat":"2",
    	"rl1_type":"0",
    	"ch_num":"1",
    	"ele_fun_en":"1",
    	"rl1_pin":"26",
    	"netn_led":"0",
    	"vol_def":"0",
    	"ch_dpid1":"1",
    	"sel_pin_lv":"1",
    	"crc":"86"
    }
    


    I was also able to upload ESPHome on it by just selecting the CB2S module and using the OTA feature to upload it. I did have to rename the file so it'd match the OTA check.
    Here is my config so far:

    
    light:
      - platform: status_led
        id: "status"
        name: "Switch state"
        pin:
          number: GPIO8
          inverted: true
    
    switch:
      - platform: gpio
        id: "relay"
        pin:
          number: GPIO26
          inverted: false
        name: "Relay"
        restore_mode: ALWAYS_ON
    
    binary_sensor:
      - platform: gpio
        pin:
          number: GPIO10
          inverted: true
        name: "Button"
        on_press:
          - switch.toggle: relay
    
    sensor:
      - platform: hlw8012
        sel_pin:
          number: GPIO24
          inverted: true
        cf_pin: GPIO7
        cf1_pin: GPIO6
        model: BL0937
        update_interval: 10s
        current:
          name: "Current"
        voltage:
          name: "Voltage"
        power:
          name: "Power"
        energy:
          name: "Energy"
    


    However, the BL0937 reads as all zeros. Any idea what I'm doing wrong?
    Unfortunately I didn't try OpenBeken extensively to see if it worked there, and there doesn't seem to be an easy way to use ESPHome OTA to upload OpenBeken.
  • #44 21275863
    mjab

    Level 10  
    Posts: 188
    Rate: 15
    Web interface of a smart WiFi socket showing device status and energy consumption. .

    
    #include <Arduino.h>
    #include "Timer.h"
    #include <ESP8266WiFi.h>
    #include <WiFiClient.h>
    #include <ESP8266WebServer.h>
    #include <ESP8266HTTPClient.h>
    #include <ESP8266HTTPUpdateServer.h>
    #include <ESP8266mDNS.h>
    #include <EEPROM.h>
    
    Timer t;
    WiFiClient client;
    ESP8266WebServer server(80);
    ESP8266HTTPUpdateServer httpUpdater;
    
    
    // --- USTAWIENIA ---
    const String plyta = "BLOW WiFi SMART SOCKET [EAN : 5900804116486]"; // Producent i model płyty głównej urządzenia
    const char *OSver = "v.6.4"; // Wersja oprogramowania
    const char *deviceName = "jGniazdko-WiFi-01"; // Nazwa widoczna w routerze (bramie)
    
    const int StartStan = 1; // Domyślny stan po zaniku prądu [ 1 - Właczony | 0 - Wyłaczony ]
    int BlokadaPrzycisku = 0; // Stan blokady przycisku na urzadzeniu [ 1 - Przycisk zabokowany | 0 - Przycisk odblokowany ]
    
    const float WspolczynnikV = 0.1207; // Współczynnik napięcia (V) [KALIBRACJA]
    const float WspolczynnikW = 1.3302; // Współczynnik mocy (W) [KALIBRACJA]
    const float MaxWaty = 2300; // Cyfrowy bezpiecznik, gdy moc przekroczy tę wartośc to rozłaczy przekaźnik.
    
    const float MinVolty = 0; // Napięcie sieciowe poniżej którego przekaźnik zostanie rozłaczony.
    const float MaxVolty = 0; // Napięcie sieciowe powyżej którego przekaźnik zostanie rozłaczony.
    const String NormaEnergetyczna = "PN-IEC 60038"; // Numer normy energetycznej w danej sieci.
    
    const char *ssid =  "xxxxx"; // Nazwa sieci WiFi
    const char *pass =  "xxxxxxxx"; // Hasło do sieci WiFi
    
    const String DOMOTICZ_SERVER_IP = "[LOGIN[:[HASŁO]@192.168.x.x:8080"; // Login, hasło i adres IP wraz z portem do serwera Domoticz
    const int IDXprzekaznik = 285; // Numer IDX w Domoticz typu "przełącznik". Do zarządzania przekaźnikiem.
    const int IDXmiernik = 284; // Numer IDX w Domoticz typu "zużycie (energia elektryczna)". Do pomiarów zużytej energii.
    // ------
    
    
    int StanPrzycisku = 0;
    int StanPrzekaznikDomoticz = 2;
    
    
    String getValue(String data, char separator, int index) { 
     int found = 0; 
     int strIndex[] = {0, -1}; 
     int maxIndex = data.length()-1; 
         
     for (int i=0; i<=maxIndex && found<=index; i++) { 
      if (data.charAt(i)==separator || i==maxIndex) { 
       found++; 
       strIndex[0] = strIndex[1]+1; 
       strIndex[1] = (i == maxIndex) ? i+1 : i; 
      }
     }
         
     return found>index ? data.substring(strIndex[0], strIndex[1]) : ""; 
    }
    
    
    void EEPROM_ZAPIS(String buffer, int N) {
     digitalWrite(14, LOW);
     EEPROM.begin(512);
     delay(5);
    
     for (int L = 0; L < 32; ++L) {
      EEPROM.write(N + L, buffer[L]);
     }
    
     EEPROM.commit();
     delay(5);
     digitalWrite(14, HIGH);
    }
    
    
    String EEPROM_ODCZYT(int min, int max) {
     digitalWrite(14, LOW);
     EEPROM.begin(512);
     delay(10);
     String buffer;
     for (int L = min; L < max; ++L) if (isAlphaNumeric(EEPROM.read(L))) buffer += char(EEPROM.read(L));    
     return buffer;
     digitalWrite(14, HIGH);
    }
    
    
    // BL0937
    float Volty = 0.00;
    float Waty = 0.00;
    float Ampery = 0.00;
    float kWh = 0.000;
    int kwhMEM = 0;
    String DanekWhOLD = "";
    int bezpiecznik = 0;
    int UszkodzonyPrzekaznik = 0;
    int BledneVolty = 0;
    volatile unsigned long pulseCountCF = 0;
    volatile unsigned long pulseCountCF1 = 0;
    
    // Liczenie inpulsów napięcia (V)
    void IRAM_ATTR onPulseCF() {
      pulseCountCF++;
    }
    
    // Liczenie inpulsów mocy (W)
    void IRAM_ATTR onPulseCF1() {
      pulseCountCF1++;
    }
    
    void miernik() {
      noInterrupts(); // Wyłączamy przerwania na czas odczytu
      unsigned long pulsesCF = pulseCountCF;
      unsigned long pulsesCF1 = pulseCountCF1;
      pulseCountCF = 0;
      pulseCountCF1 = 0;
      interrupts(); // Włączamy przerwania z powrotem
    
      Volty = pulsesCF * WspolczynnikV;  // Napięcie (V)
      Waty = pulsesCF1 * WspolczynnikW;  // Moc (W)
      
      if (Volty > 0 and Waty > 0) {
        Ampery = Waty / Volty;  // Obliczamy prąd (A)
      } else Ampery = 0.00;
    
      if (Volty > 0) {
        if (Waty > MaxWaty+5 and digitalRead(15) == HIGH) {
          bezpiecznik = 1;
          digitalWrite(15, LOW);
          WyslijStanPrzekaznika();
        } else if (Waty > 0 and digitalRead(15) == LOW and UszkodzonyPrzekaznik == 0) {
          UszkodzonyPrzekaznik = 2;
          digitalWrite(15, LOW);
          WyslijStanPrzekaznika();
        } else if (Waty > 0 and digitalRead(15) == LOW and UszkodzonyPrzekaznik == 2) {
          UszkodzonyPrzekaznik = 1;
          digitalWrite(15, LOW);
          WyslijStanPrzekaznika();
        } else if (UszkodzonyPrzekaznik == 1) UszkodzonyPrzekaznik = 0;
    
        if (Volty < MinVolty and Volty > 0 and BledneVolty == 0 and digitalRead(15) == HIGH) { 
          BledneVolty = 1;  
          digitalWrite(15, LOW);
          WyslijStanPrzekaznika();
        }
        else if (Volty > MaxVolty and BledneVolty == 0 and digitalRead(15) == HIGH) { 
          BledneVolty = 2; 
          digitalWrite(15, LOW);
          WyslijStanPrzekaznika();
        }
    
        // Liczenie zużycia energii w kWh
        kWh += (Waty / 1000.0) / 3600.0;  // Waty na kilowatogodziny w ciągu 1 sekundy
        
        int whole_kWh = floor(kWh);
        if (whole_kWh > kwhMEM) {
         kwhMEM = whole_kWh;
         ZapisStanuLicznika();
        }
    
        if (IDXmiernik > 0) {
          String DomoticzDane = String(Waty,0) + ";" + String(kWh,0);
          DomoticzDane.replace(" ", "");
          if (DanekWhOLD != DomoticzDane) {
           DanekWhOLD = DomoticzDane;
           domoticz(IDXmiernik, "0", DomoticzDane);
          }
        }
      }
    }
    
    void ZapisStanuLicznika() {
     String eepromTXT = String(kwhMEM) + ":";
     EEPROM_ZAPIS(eepromTXT, 0);
    }
    // ---
    
    
    // Obsługa przycisku
    void SprawdzPrzycisk() {
     if (BlokadaPrzycisku == 0) {
      int przycisk = digitalRead(0);
      if (przycisk == LOW and StanPrzycisku == 0) {
       StanPrzycisku = 1;
       bezpiecznik = 0;
       BledneVolty = 0;
       digitalWrite(15, !digitalRead(15));
       WyslijStanPrzekaznika();
      } else if (przycisk == HIGH and StanPrzycisku == 1) StanPrzycisku = 0;
     }
    }
    
    
    // Strona www urządzenia
    void www() {
     String html = "<html><head><meta charset='UTF-8'>";
     html += "<style>";
     html += "body { background-color: black; color: white; }";
     html += "table { width: 800px; background-color: white; color: black; border-spacing: 10px; }";
     html += "th { background-color: orange; }";
     html += ".color-bar { height: 32px; width: 100%; background: linear-gradient(to right, green, yellow, red); }";
     html += ".progress-bar { width: 100%; background-color: gray; }";
     html += "h1 { color: white; }";
     html += ".center-cell { text-align: center; }";
     html += ".option-column { width: 25%; }";
     html += ".value-column { width: 75%; }";
     html += ".status-active { color: darkred; }";
     html += ".status-inactive { color: darkgreen; }";
     html += "button { padding: 10px; background-color: orange; border: none; color: white; font-size: 16px; cursor: pointer; }";
     html += "</style>";
     html += "<meta http-equiv=\"refresh\" content=\"2; url=/\"></head><body>";
     html += "<center><h1>- " + String(deviceName) + " -</h1>";
     html += "<table>";
     html += "<tr><th class='option-column'>Opcja</th><th class='value-column'>Wartość</th></tr>";
     html += "<tr><td style='text-align: right;'>Płyta główna : </td><td style='text-align: left;'><b>" + String(plyta) + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'>Wersja programu : </td><td style='text-align: left;'><b>" + String(OSver) + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'> </td><td style='text-align: left;'> </td></tr>";
    
     if (bezpiecznik == 1) html += "<tr><td style='text-align: right;'><u>Bezpiecznik : </td><td style='text-align: left;'><b class='status-active'>Przekroczono " + String(MaxWaty,0) + "W, zasilanie odłączono !</b></td></tr>";
     if (UszkodzonyPrzekaznik == 1) html += "<tr><td style='text-align: right;'><u>Awaria : </td><td style='text-align: left;'><b class='status-active'>Przekaźnik jest rozłaczony i dalej występuje pobór prądu !</b></td></tr>";
     if (BledneVolty == 1) html += "<tr><td style='text-align: right;'><u>Uwaga : </td><td style='text-align: left;'><b class='status-active'>Wykryto napięcie [V] <u>niższe</u> niż dopuszcza norma " + String(NormaEnergetyczna) + " !<br> [Zasilanie wyłaczono] </b></td></tr>";
     else if (BledneVolty == 2) html += "<tr><td style='text-align: right;'><u>Uwaga : </td><td style='text-align: left;'><b class='status-active'>Wykryto napięcie [V] <u>wyższe</u> niż dopuszcza norma " + String(NormaEnergetyczna) + " !<br> [Zasilanie wyłaczono] </b></td></tr>";
     if (bezpiecznik == 1 or UszkodzonyPrzekaznik == 1 or BledneVolty > 0) html += "<tr><td style='text-align: right;'> </td><td style='text-align: left;'> </td></tr>";
     
     html += "<tr><td style='text-align: right;'>IP : </td><td style='text-align: left;'><b>" + WiFi.localIP().toString() + "</b></td></tr>";
     html += "<tr><td style='text-align: right;'>MAC : </td><td style='text-align: left;'><b>" + WiFi.macAddress() + "</b></td></tr>";
     int SilaWiFi = WiFi.RSSI();
     if (SilaWiFi < -65) html += "<tr><td style='text-align: right;'>Sygnał WiFi : </td><td style='text-align: left;'><b>" + String(SilaWiFi) + " dBm</b> [ SLABY SYGNAŁ ! ]</td></tr>";
     else html += "<tr><td style='text-align: right;'>Sygnał WiFi : </td><td style='text-align: left;'><b>" + String(SilaWiFi) + " dBm</b></td></tr>";
     html += "<tr><td style='text-align: right;'> </td><td style='text-align: left;'> </td></tr>";
     html += "<tr><td style='text-align: right;'><u>V</u>olty : </td><td style='text-align: left;'><b>" + String(Volty,1) + " V</b></td></tr>";
     if (Ampery < 0.1 and Ampery > 0) html += "<tr><td style='text-align: right;'><u>A</u>mpery : </td><td style='text-align: left;'><b>" + String(Ampery,2) + " A</b></td></tr>";
     else html += "<tr><td style='text-align: right;'><u>A</u>mpery : </td><td style='text-align: left;'><b>" + String(Ampery,1) + " A</b></td></tr>";
     if (kWh > 0) html += "<tr><td style='text-align: right;'>Licznik : </td><td style='text-align: left;'><b>" + String(kWh,3) + " kWh</b></td></tr>";
     html += "<tr><td style='text-align: right;'><u>W</u>aty : </td><td>";
    
     float wattPercentage = (Waty / MaxWaty) * 100;
     if (wattPercentage > 100) wattPercentage = 100;
     else if (Waty < 1) wattPercentage = 0;
    
     html += "<div class='progress-bar'><div class='color-bar' style='width: " + String(wattPercentage) + "%;'></div></div>";
     if (Waty > 0 and Waty < 1) html += "<span><center>" + String(Waty, 1) + " W <i>/ " + String(MaxWaty,0) + " W</i>  [" + String(wattPercentage, 0) + "% ]</center></span></td></tr>";
     else html += "<span><center>" + String(Waty, 0) + " W <i>/ " + String(MaxWaty,0) + " W</i>  [" + String(wattPercentage, 0) + "% ]</center></span></td></tr>";
     html += "</table><br>";
    
     html += "<table>";
     html += "<tr><th colspan='2'>Zasilanie</th></tr>";
     html += "<tr><td style='text-align: center;'>Stan zasilania : </td><td>";
    
     int StanGPIO15 = digitalRead(15);
     if ((StanGPIO15 == HIGH and Volty > 0) or Waty > 0) html += "<b class='status-active'>[Aktywne] </b> <i> [Uwaga na wysokie napięcie !]</i></td></tr>";
     else html += "<b class='status-inactive'>[Wyłączone]</b></td></tr>";
     html += "<tr><td colspan='2' style='text-align: center;'>";
    
     if (BlokadaPrzycisku == 0) html += " <button onclick=\"window.location.href='ustaw?blokada=tak'\">Zablokuj</button> ";
     else html += " <button onclick=\"window.location.href='ustaw?blokada=nie'\">Odblokuj</button> ";
     
     if (StanGPIO15 == HIGH) html += " <button onclick=\"window.location.href='ustaw?zasilanie=0'\">Wyłącz</button> ";
     else html += " <button onclick=\"window.location.href='ustaw?zasilanie=1'\">Włącz</button> ";
     
     html += " <button onclick=\"window.location.href='ustaw?wyzeruj=tak'\">Reset kWh</button> ";
     html += " <button onclick=\"window.location.href='ustaw?restart=tak'\">Restart</button> ";
     html += " <button onclick=\"window.location.href='update'\">Aktualizacja</button> ";
     html += "</td></tr>";
     html += "</table>";
     html += "<br><br><br><i>Prawa autorskie - Michał Jabłoński (www.k.j.pl) - Jabłoński KOMPUTERY</i></center></body></html>";
    
     server.send(200, "text/html", html);
    }
    
    // Funkcja do obsługi żądań GET na ścieżce "/ustaw"
    void handleUstaw() {
      if (server.hasArg("zasilanie")) {
        String zasilanie = server.arg("zasilanie");
        if (zasilanie == "0") {
          digitalWrite(15, LOW);  // Wyłącz przekaźnik
          WyslijStanPrzekaznika();
        } else if (zasilanie == "1") {
          bezpiecznik = 0;
          BledneVolty = 0;
          digitalWrite(15, HIGH);  // Włącz przekaźnik
          WyslijStanPrzekaznika();
        }
        server.sendHeader("Location", "/", true);
        server.send(302, "text/plain", "");  // 302 Moved Temporarily, przekierowanie
        return;
      }
    
      if (server.hasArg("blokada")) {
        String bl = server.arg("blokada");
        if (bl == "nie") {
          BlokadaPrzycisku = 0;
        } else if (bl == "tak") {
          BlokadaPrzycisku = 1;
        }
        server.sendHeader("Location", "/", true);
        server.send(302, "text/plain", "");  // 302 Moved Temporarily, przekierowanie
        return;
      }
    
      if (server.hasArg("wyzeruj")) {
        kWh = 0.000;
        kwhMEM = kWh;
        ZapisStanuLicznika();
        String html = "<html><head><meta http-equiv=\"refresh\" content=\"5; url=/\"></head><body>TRWA WYZEROWANIE DANYCH kWh ...</body></html>";
        server.send(200, "text/html", html);
        return;
      } else if (server.hasArg("kwh")) {
        String kwhValue = server.arg("kwh");
        kWh = kwhValue.toFloat();
        kwhMEM = kWh;
        ZapisStanuLicznika();
        String html = "<html><head><meta http-equiv=\"refresh\" content=\"5; url=/\"></head><body>TRWA ZAPIS DANYCH NA " + String(kwhMEM) + " kWh ...</body></html>";
        server.send(200, "text/html", html);
        return;
      }
    
      if (server.hasArg("restart")) {
        String restartValue = server.arg("restart");
        if (restartValue == "tak") {
          String html = "<html><head><meta http-equiv=\"refresh\" content=\"10; url=/\"></head><body>TRWA RESTART ...</body></html>";
          server.send(200, "text/html", html);
          delay(250);
          ESP.restart();
        }
      }
    
      server.sendHeader("Location", "/", true);
      server.send(302, "text/plain", "");  // 302 Moved Temporarily, przekierowanie
    }
    
    
    void Polacz_WiFi() {
      if (WiFi.status() != WL_CONNECTED) {
       Serial.println("Laczenie z WiFi");
    
       WiFi.setOutputPower(20.5);
       WiFi.begin(ssid, pass);
       WiFi.hostname(deviceName);
      
       while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        digitalWrite(14, HIGH);
        delay(100);
        digitalWrite(14, LOW);
        delay(100);
       }
       
       Serial.println("");
       Serial.println("Przydzielono adres IP: ");
       Serial.print(WiFi.localIP());
       Serial.println("");
       digitalWrite(14, HIGH);
      }
    }
    
    
    // Wysyłanie danych do Domoticz
    void domoticz(int idx, String nvalue, String svalue) {
      Serial.println(" - Wysylam dane do domoticz...");
      nvalue.replace(" ", "");
      svalue.replace(" ", "");
      HTTPClient http;
      String adres = "http://" + DOMOTICZ_SERVER_IP + "/json.htm?type=command&param=udevice&idx=" + String(idx) + "&nvalue=" + nvalue + "&svalue=" + svalue;
      Serial.print("     URL : "); Serial.println(adres);
      http.begin(client, adres);
      http.addHeader("Content-Type", "application/html");
      int httpCode = http.GET();
      http.end();
      Serial.println("    Dane do Domoticz zostaly wyslane.");
    }
    
    // Wysyłanie aktualnego stanu pzekaźnika do Domoticz
    void WyslijStanPrzekaznika() {
     int StanLed = digitalRead(14);
     int StanPrzekaznika = digitalRead(15);
    
     if (StanLed == HIGH and StanPrzekaznika == HIGH) digitalWrite(14, LOW);
     else if (StanLed == LOW and StanPrzekaznika == LOW) digitalWrite(14, HIGH);
     
     if (IDXprzekaznik > 0) { 
      if (StanPrzekaznikDomoticz != StanPrzekaznika) {
       StanPrzekaznikDomoticz = StanPrzekaznika;
       domoticz(IDXprzekaznik, String(StanPrzekaznika), "");
      }
     }
    }
    
    
    void setup() {
     Serial.begin(115200);
    
     pinMode(0, INPUT_PULLUP); // Przycisk (LOW = Wciśnięty)
     pinMode(4, INPUT_PULLUP); // CF
     pinMode(5, INPUT_PULLUP); // CF1
     pinMode(12, OUTPUT); // SELi  [ LOW = Ampery | HIGH = Volty ]
     pinMode(14, OUTPUT); // LED niebieska [Invert - LOW to świeci]
     pinMode(15, OUTPUT); // Przekaźnik
    
     digitalWrite(12, HIGH);
     digitalWrite(14, LOW);
     if (StartStan == 1) digitalWrite(15, HIGH);
     else digitalWrite(15, LOW);
    
      delay(1000);
    
     // Odczyt z EEPROM
     String EEPROMtxt = EEPROM_ODCZYT(0, 64);
     if (getValue(EEPROMtxt, ':', 0).toInt() > 0) kWh = getValue(EEPROMtxt, ';', 0).toFloat(); // Odczyt stanu całkowitego kWh
    
     Polacz_WiFi();
     delay(250);
    
     httpUpdater.setup(&server);
     server.on("/", www);
     server.on("/ustaw", HTTP_GET, handleUstaw);
     server.begin();
    
     attachInterrupt(digitalPinToInterrupt(4), onPulseCF, RISING);  // CF
     attachInterrupt(digitalPinToInterrupt(5), onPulseCF1, RISING); // CF1
     t.every(1000, miernik);
    
     WyslijStanPrzekaznika();
     
     t.every(5, SprawdzPrzycisk);
     t.every(250, MiganieLED);
    }
    
    
    void loop() {
     if (WiFi.status() == WL_CONNECTED) server.handleClient();
     else if (WiFi.status() != WL_CONNECTED) Polacz_WiFi();
     
     t.update();
    }
    
    
    void MiganieLED() {
     if (bezpiecznik == 1 or BledneVolty > 0 or UszkodzonyPrzekaznik == 1) digitalWrite(14, !digitalRead(14));
     else if (digitalRead(14) == LOW and digitalRead(15) == HIGH) digitalWrite(14, HIGH);
    }
    
    .

    All the code looks like this and works. There is already button support and sending to Domoticz works.

    To control the relay in Domoticz we have the url http://192.168.x.x/ustaw?zasilanie=0/1 (1 = on, 0 = off).

    It is also possible to restart it under the url http://192.168.x.x/ustaw?restart=tak and we can change the kWh status under the url http://192.168.x.x/ustaw?kwh=1.123.

    You can update the software at http://192.168.x.x/update and upload the compiled *.bin file there.

    This works fairly stably and the measurements seem quite accurate. Apparently there are poor quality 3.3v power supplies in these plugs, and there is something in that, because after removing the cube from the power supply you can feel that the "pins" are slightly warm. However, there seems to be no way to save some current to "unload" the stabilisers. :)
    Company Account:
    Jabłoński KOMPUTERY
    Ogrodowa 3a, Rypin, 87-500 | Tel.: 507XXXXXX (Show) | Company Website: https://www.k.j.pl
  • #45 21297547
    tun0
    Level 7  
    Posts: 12
    I guess I messed up the CB2S while removing it. Using my latest USB-TTL adapter I can flash an ESP02S just fine. Flashed Tasmota on it and placed it in place of the CB2S. Took a bit of fiddling around with the configuration, but I managed to make it work, kinda. The voltage showed 298V though, which is a tad high obviously. Used VoltageSet to bring it down to 228 (based on P1 data). Have yet to do proper measurements (and calibration, if needed) to see if the rest of the numbers are okay or not.
  • #46 21512298
    kantora98
    Level 5  
    Posts: 5
    I have several smart plugs with energy meter that have the same PIN configuration as the plug in the topic, but a bit different designation RMC005. CB2S board has more recent (as I understand it) version number.
    Close-up of a circuit board with visible electronic components, markings, and traces.CB2S board with BK7231N chip and other electronic components.Close-up of a circuit board with markings RX2, TX2, and CSN.
    I flashed one of them with no problem, but had to de-solder CB2S board first. For some reason I could not make flashing work when I soldered wires in place.

    In any case, my question is this:
    In Tuya (or SmartLife) App, this plug has a few more functions like:
    - Clear power data
    - Relay status (On/Off/Remember-Last-Status)
    - Light Mode (ON/OFF/Depending-On-ON-OFF-Status)
    - Overcharge Switch (turn off in power is less than 3W for 40 minutes)
    - Child Lock

    Has anyone converted these Tuya options to OpenBK configurations?
  • #47 21512528
    p.kaczmarek2
    Moderator Smart Home
    Posts: 14431
    Help: 650
    Rate: 12399
    Clear power data - we have a command for that:
    https://github.com/openshwprojects/OpenBK7231T_App/blob/main/docs/commands.md
    Relay status - we have gui (startup menu) and command for that
    Light mode - set AlwaysHigh, or AlwaysLow, or LED or LED_n or WiFiLED
    Overcharge Switch - could be scripted, scripting samples are here, including the "trigger event when energy exceeds" https://github.com/openshwprojects/OpenBK7231T_App/blob/main/docs/autoexecExamples.md
    If you need a working script, tell me, I can try to prepare one later.
    Child Lock - I think we have flag for that https://github.com/openshwprojects/OpenBK7231T_App/blob/main/docs/flags.md
    Helpful post? Buy me a coffee.
  • #48 21514086
    kantora98
    Level 5  
    Posts: 5
    All in all, it is not just about features/commands but how to expose them to Home Assistant in a form of buttons/sliders/etc. Can you point me to explanation(s)? I'm very very new to all these things. And, I guess, I'm not the first one with such questions.

    p.kaczmarek2 wrote:

    I saw it in WEB APP, but am looking into a way to trigger it from HA.

    p.kaczmarek2 wrote:
    Relay status - we have gui (startup menu) and command for that

    Are you talking about this menu?
    Pin start value setting interface with channels for saving.


    p.kaczmarek2 wrote:
    Light mode - set AlwaysHigh, or AlwaysLow, or LED or LED_n or WiFiLED

    Can you point me to a link where I can read about these? (Found the doc about it)

    p.kaczmarek2 wrote:
    Overcharge Switch - could be scripted, scripting samples are here, including the "trigger event when energy exceeds" https://github.com/openshwprojects/OpenBK7231T_App/blob/main/docs/autoexecExamples.md
    If you need a working script, tell me, I can try to prepare one later.

    Such script might be usable not only for me. When I plug the plug into socket (with metering capabilities) the plug initializes connection but stays OFF (by default), and meter shows that the plug consumes around 0.2Wh in "wifi-connected" state, but once I press the On/Off switch the metering socket starts to show 1.1-1.2Wh. So, I guess, people which are used to save every bit of electricity might benefit from such script.

    p.kaczmarek2 wrote:

    From the description I understood that once flag set it will ignore any button presses, however in original Tuya firmware you had to hold the button pressed for 4 seconds to unlock the child lock. How does current implementation work?

    Thanks for your time.

Topic summary

✨ The discussion centers on the RMC021 WiFi smart plug featuring a BK7231N/CB2S chip and a BL0937 energy measurement IC. Users shared experiences with disassembling the device, desoldering the CB2S module to enable flashing with OpenBK firmware using BK7231Flasher on Linux Mint, and challenges related to RX/TX line interference and USB-to-UART adapter compatibility. Several contributors provided detailed pin configurations for BL0937 integration, flashing procedures, and JSON templates for device inclusion in OpenBekenIOT device lists. Alternative firmware approaches include ESPHome and Tasmota on ESP8266 modules replacing the CB2S, with calibration challenges for voltage and current measurement pulses from BL0937. Advanced users developed custom Arduino IDE code for pulse counting via interrupts to improve measurement accuracy. Additional features from the original Tuya firmware such as power data clearing, relay status modes, light modes, overcharge protection, and child lock were discussed with references to OpenBK commands, scripting, and flags to replicate these functions. The conversation also covered hardware repair advice for damaged relays and desoldering techniques, as well as suggestions for integrating the smart plug with home automation platforms like Home Assistant and Domoticz.
Generated by the language model.

FAQ

TL;DR: For cheap RMC021-style smart plugs, a 10€ for 3 plugs OpenBeken conversion is practical: one user said it flashed "smooth as butter" after removing the CB2S, setting the correct BL0937 pins, and calibrating voltage, current, and power. This FAQ helps anyone replacing Tuya firmware on BK7231N/CB2S plugs with energy metering. [#21275404]

Why it matters: This thread turns a low-cost Tuya plug into a documented OpenBeken, ESPHome, or ESP8266 conversion path with known pins, failure modes, and calibration methods.

Option Hardware path Energy metering status Main caveat
OpenBeken on CB2S/BK7231N Flash original module Working with BL0937 driver In-circuit UART often fails
ESPHome on CB2S OTA upload reported Relay/LED/button worked, BL0937 read 0 Metering config/pulse handling issue
Tasmota on ESP02S/TYWE2S Replace Tuya module Working after calibration Needs rewiring and module swap
Custom ESP8266 code Replace Tuya module Working after interrupt-based counting More firmware work required

Key insight: The plug is usable and well-mapped, but UART flashing usually becomes reliable only after desoldering the CB2S. Once flashed, the known BL0937, relay, button, and LED pins make setup straightforward.

Quick Facts

  • Confirmed RMC021 mapping for the BK7231N/CB2S version is: P6=BL0937CF1, P7=BL0937CF, P8=WiFiLED, P10=Button, P24=BL0937SEL, P26=Relay. [#20991558]
  • One user bought 3 additional plugs for a total of 10€, showing these Tuya-based energy plugs are often cheaper than branded alternatives. [#20991558]
  • A practical desoldering target temperature reported by an experienced contributor was 350°C on a KSGER station for CB2S/WB2S modules. [#21127144]
  • Reported calibration and protection values in custom ESP8266 code included 2300 W max load, 0.1207 voltage factor, and 1.3302 power factor. [#21275863]

How do I flash an RMC021 smart plug with a BK7231N/CB2S module and BL0937 power meter using OpenBeken?

Flash it by removing the CB2S, programming it externally, then restoring it to the board. 1. Desolder the CB2S and connect 3.3V, GND, RX, and TX to a USB-UART adapter. 2. Use Easy UART Flasher or BK7231Flasher for BK7231N, then boot OpenBeken and open http://192.168.4.1. 3. Assign P6=BL0937CF1, P7=BL0937CF, P8=WiFiLED, P10=Button, P24=BL0937SEL, P26=Relay, then run startDriver BL0937. [#21092556]

Why does flashing the CB2S in-circuit fail on this smart plug, and why does desoldering the module often make UART flashing work?

In-circuit flashing fails because the plug board can load or disturb the UART lines, especially RX. The first report says the switch circuitry and other parts interfered with the RX path, so the module was fully removed instead of cutting traces. After desoldering, flashing worked, the original firmware was dumped, and OpenBeken was installed successfully. That makes desoldering the cleanest path when UART access is unstable on this board. [#20991558]

What is the BL0937 chip, and how does it measure voltage, current, and power in a smart plug?

"BL0937 is a power-metering IC that outputs pulse signals for mains measurements, using one pin for power and another multiplexed pin for voltage or current." In this plug, CF carries power pulses, CF1 carries voltage or current depending on SEL, and OpenBeken uses a dedicated BL0937 driver. The thread repeatedly confirms voltage calibration is easy, while current and power need load-based calibration. [#21512528]

What is a CB2S or WB2S module, and how does it relate to BK7231N and BK7231T devices?

"CB2S or WB2S is a Tuya Wi-Fi module board that hosts a Beken SoC, exposes GPIOs to the appliance PCB, and can be replaced or reflashed for custom firmware." In this thread, the RMC021 uses CB2S with BK7231N, while a related RMC001 variant uses WB2S with BK7231T. Both modules appear across RMC-family plugs, but the pin map can differ between board variants. [#21135558]

Which GPIO pins should I assign in OpenBeken for the RMC021 smart plug with BK7231N, CB2S, and BL0937?

Use the OpenBeken template with six active assignments. Set GPIO6 to BL0937CF1, GPIO7 to BL0937CF, GPIO8 to WifiLED_n or WiFiLED, GPIO10 to Btn, GPIO24 to BL0937SEL, and GPIO26 to Rel. This exact JSON template was posted for the RMC021 and matched the extracted Tuya configuration. If you copy another RMC-family plug template, verify the LED and BL0937 pins first. [#20991558]

What’s the proper way to calibrate voltage, current, and power readings on a BL0937-based OpenBeken smart plug?

Calibrate with known loads and use the OpenBeken correction commands. One user got acceptable results when a 60 W heating bulb read 60 W and a 7.5 W LED bulb read 7.5 W, then fine-tuned with VoltageSet, CurrentSet, and PowerSet. Start with voltage, because it is easiest to verify. Then use a stable resistive load for current and power, not a vague household device. [#21092556]

How can I back up the original Tuya firmware from a BK7231N plug when BK7231Flasher fails during the read process?

Retry the backup first, because the read path should use the same mechanics as flashing. One failed read showed Reading 0x00... failed after detecting flash MID 1560EB, even though write operations had worked. The maintainer advised trying again and adjusting timeout or retry values in the advanced tab. If backup still fails, keep the extracted Tuya config, because it preserves the key pin mapping. [#21107616]

What reply delay, timeout, or retry settings help when BK7231Flasher cannot reliably read or flash a CB2S module?

A reply delay of 5 was explicitly reported to fix a stubborn CB2S flashing case. Another user wrote that flashing problems disappeared after removing the module and setting reply delay to 5. The maintainer also suggested experimenting with timeout and retry values in BK7231Flasher’s advanced settings when reads fail. Use these adjustments only after confirming wiring and power are correct. [#21275404]

Why does BK7231Flasher get stuck on "get bus" with a CB2S module, and how do RX/TX wiring and USB-to-UART power quality affect it?

“Get bus” usually fails because wiring is wrong, reboot timing is wrong, or the USB-UART adapter cannot supply clean 3.3 V current. The thread caught at least one wiring mistake where RX was connected to RX instead of crossing RX-TX and TX-RX. The maintainer also questioned the adapter’s LDO quality and whether the device was rebooted by disconnecting VDD. Weak adapters can flash inconsistently even when the module itself is fine. [#21147683]

What soldering temperature and technique work best for desoldering a CB2S or WB2S module without lifting pads?

Use a relatively low iron temperature and remove solder patiently from all pads before lifting the module. An experienced contributor said, “I’m trying to keep low temperature, on my KSGER I use 350C.” The thread also mentions flux, braid, and careful wick work. If pads start lifting, stop pulling and clear more solder first. Mechanical force damages these cheap plug PCBs faster than heat does. [#21127144]

How do OpenBeken, ESPHome, and Tasmota compare for converting Tuya smart plugs with BL0937 energy monitoring?

OpenBeken is the most complete path shown here for the original CB2S hardware, because the posted pin map and BL0937 driver worked on the RMC021. ESPHome booted on CB2S by OTA, but one report had relay, LED, and button working while BL0937 stayed at zero. Tasmota worked after replacing CB2S with an ESP02S and recalibrating voltage from 298 V down to 228 V. Choose OpenBeken for least hardware change, or Tasmota if you already prefer ESP8266. [#21297547]

Which ESP8266 replacement modules can take the place of a CB2S or WB2S board in these RMC-family smart plugs, and what extra wiring is needed?

TYWE2S and ESP02S were explicitly named as compatible replacement modules. For a full ESP8266 conversion, one user mapped button to GPIO0, SEL to GPIO12, relay to GPIO15, CF1 to GPIO4, CF to GPIO5, and LED to GPIO2, with 3.3 V and GND carried over. Another report noted that an ESP8266MOD also worked, with EN pulled up by 10 kΩ and GPIO15 pulled down by 10 kΩ. [#21148742]

Why does the BL0937 sensor in ESPHome show all zeros on a CB2S-based plug even when the relay, LED, and button work?

The most likely cause is metering configuration, not the basic GPIO setup. In the posted ESPHome config, relay, LED, and button all worked, but the hlw8012 sensor for model BL0937 returned zeros. The thread’s follow-up points to calibration and correct CF, CF1, and SEL handling as the critical part. If your pin map is right, confirm SEL polarity, pulse activity on CF/CF1, and whether your firmware actually toggles the measurement mode correctly. [#21275404]

How should I measure BL0937 CF and CF1 pulses on ESP8266 for accurate readings, and why are interrupts better than digitalRead polling?

Use interrupts and count pulses over time instead of polling with digitalRead. The maintainer called polling “bad, imprecise and blocks program execution” and recommended attaching GPIO interrupts once to CF and CF1, incrementing counters in the ISR, then reading counts every second and calibrating pulses per second. That method stabilized a custom ESP8266 build and avoided single-pulse timing noise. It also leaves enough CPU time for Wi-Fi, web UI, and relay control. [#21273841]

How can I expose OpenBeken features like clear power data, relay startup state, LED mode, overcharge auto-off, and child lock to Home Assistant as buttons or controls?

Expose them through OpenBeken commands, startup settings, pin roles, and scripts, then surface those actions in Home Assistant. The maintainer confirmed there is a command for clearing power data, a startup GUI and command for relay state, LED modes such as AlwaysHigh, AlwaysLow, LED, LED_n, and WiFiLED, script examples for threshold-triggered energy actions, and a flag for child lock. The thread stops short of a finished HA dashboard, but the control primitives already exist in OpenBeken. [#21512528]
Generated by the language model.
ADVERTISEMENT