Project Source


RL_smart_meter/RL_smart_meter.html

/*
 * Connect to IR-READER to get SMLV1.04 Smart Reader data
 * 
 * features:
 * -  run WebServer on fix local IP 192.168.178.23
 * -  read actual smart reader data on request
 *    (W, kwh, meterNo) and some technical data
 * 
 * -  put smart_meter data to rlhome.noip.me
 * 
 * -  update tech-data  
 *    http://192.168.178.23/update?W_diff=15
 *    http://192.168.178.23/update?verbose=1
 *    http://192.168.178.23/update?fingerprint=hexString...
 *    http://192.168.178.23/update?fingerprint=82c27a1df141a43e726adfc808144fbf88b04636
 * 
 * design Notes:
 * -  use serial 57600 Baud to get println
 * -  use https connect to rlhome.noip.me
 * -  update SH1 fingerprint
 * -  use SoftwareSerial to handle 2 Serials (IR-Reader and println)
 * -  use err counters
 * 
 * 
 */
#include <SoftwareSerial.h>                 // allow more SERIAL ports
#include <ESP8266WiFi.h>                    // somw basic
#include <WiFiClientSecure.h>               // to setup WWW Server and Client
#include <EEPROM.h>                         // to store data and get these after reboot again

const char* ssid = "Rolf";                  // on 8266
const char* password = "xxxxxxxxxxxxxxxxxxxxxxxx";
const int MAX = 512;
static int buffer[MAX];                     // buffer
static String SML_buffer;                   // IR buffer in HEX
static int pos = 0;                         // pointer into Buffer
static int W = 0;                           // read Watt
static int kWh = 0;                         // read kWh
static int W_diff = 10;                     // W difference
static int W_last = 0;                      // last read W value
WiFiServer server_8266(80);                 // allow Port 80 Http requests on server running on 8266
static int err_srv = 0;                     // error counter since last boot
static int err_client = 0;                  // error counter client
static int err_read = 0;                    // error counter for read IR Buffer
static bool verbose = true;                // more or less output to Serial
static String meterNo;
SoftwareSerial mySerial (13, 15);           // GPIO13 and GPIO15
char fingerprint[] PROGMEM = "82c27a1df141a43e726adfc808144fbf88b04636";
char version[]  = "2023-01-13";

int getInteger(int i) {
  /* read 4 Bytes from pos i and return as integer
  */
  return (
           buffer[i + 0] * 0x1000000 +
           buffer[i + 1] * 0x10000 +
           buffer[i + 2] * 0x100 +
           buffer[i + 3]);
}

bool sequence(int i, int b0, int  b1, int b2, int b3, int  b4, int b5, int b6, int b7) {
  return (buffer[i] == b0 &&
          buffer[i + 1] == b1 &&
          buffer[i + 2] == b2 &&
          buffer[i + 3] == b3 &&
          buffer[i + 4] == b4 &&
          buffer[i + 5] == b5 &&
          buffer[i + 6] == b6 &&
          buffer[i + 7] == b7);
}

String getMeterNo(int i, int len) {
  /*
     0B 0A 01 49 54 52 00 03 49 c9 36
      |------> 11 Octet String incl. TL
         |---> 10 digits
            1  I  T  R 0055167286
  */
  int n = getInteger(i + 5);
  char m[11];
  sprintf (m, "%010d", n);

  return String(buffer[i]) + char(buffer[i + 1]) + char(buffer[i + 2]) + char(buffer[i + 3]) + m;
}

bool get_W_kWh(int n) {
  kWh = 0;        // init
  W = 0;          // init
  meterNo = "";
  for (int i = 0; i < n; i++) {
    if (kWh == 0 && sequence(i, 0x77, 0x07, 0x01, 0x00,  0x01, 0x08, 0x00, 0xFF)) {
      kWh = getInteger(i + 23);
      Serial.println("kWh found " + String(kWh));
    }
    if (W == 0 && sequence(i, 0x77, 0x07, 0x01, 0x00,  0x10, 0x07, 0x00, 0xFF)) {
      W = getInteger(i + 15);
      Serial.println("W found " + String(W));
    }
    if (sequence(i, 0x77, 0x07, 0x01, 0x00,  0x60, 0x01, 0x00, 0xFF)) {
      meterNo = getMeterNo(i + 14, 10);
      Serial.println("meterNo found "+meterNo);
    }
    if (meterNo.length() > 0 && W > 0 && kWh > 0) return true; // abort if values found
  }
  Serial.println("failed to read W or kWh or meterNo");
  err_read++;
  return false;                         // values not found!!
}

void putData() {
  /*
      new Data to put to Web_server
  */
  digitalWrite(D4, LOW);   // turn the LED on
  
  Serial.println("put Data to Server");
  W_last = W;
  String host = "rlhome.noip.me";
  String urlGet = "/body/8266/append.php?power=" + String(W) + "&kwh=" + String(kWh);
  const int httpsPort = 443;

  WiFiClientSecure httpsClient;    //Declare object of class WiFiClient
  httpsClient.setFingerprint(fingerprint);
  int retry = 0;
  while ((!httpsClient.connect(host, httpsPort)) && retry < 50) {
    delay(2);
    retry++;
  }
  if (retry >= 50) {
    err_client++;
    digitalWrite(D4, HIGH);   // turn the LED off
    return;
  }

  /*
     start to push data
  */
  
  httpsClient.println("GET " + urlGet + " HTTP/1.1");
  httpsClient.print("Host: ");
  httpsClient.println(host);
  httpsClient.println("User-Agent: ESP8266/1.0");
  httpsClient.println("Connection: close\r\n\r\n");

  digitalWrite(D4, HIGH);   // turn the LED off
  
}


String getValue(String data, char separator, int index) {
  /*
     String part01 = getValue(application_command,';',0);
  */
  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 do_HTML_request() {
  // Prüfen, ob sich ein Client verbunden hat
  WiFiClient client = server_8266.available();
  if (!client) {
    return;
  }
  
  // Warten auf Daten vom Client
  int retry = 0;
  while (!client.available() && retry < 50) {
    delay(1);
    retry++;
  }

  if (retry >= 50) {
    err_srv++;    // inc err counter
    return;   // exit to avoid endless loop
  }

  // Erste Zeile des Requests lesen
  String request = client.readStringUntil('\r');
  Serial.println(request);
  if (request.indexOf("get") > 0) {
    /* get some data back */
    client.flush();
    HTMLresponseGet(client);
  }
  else {
    if (request.indexOf("update") > 0) {

      String keys = getValue(request, '?', 1);
      String key = getValue(keys, '=', 0);
      String val = getValue(keys, '=', 1);

      if (key.equals("W_diff")) {
        W_diff = val.toInt();
        EEPROM.put(0, W_diff);
        EEPROM.commit();
      } else if (key.equals("verbose")) {
        verbose = (val.toInt() == 1);
        EEPROM.put(1, verbose);
        EEPROM.commit();
      } else if (key.equals("fingerprint")) {
        val.toCharArray(fingerprint, sizeof(fingerprint));
      } else {
        Serial.println("*** unknown key=" + key);
      }
    }
    client.flush();
    HTMLresponse(client);
  }
}


//////////////////////////////////////////////////////////////

 bool read_SML_buffer() {
  // get any incoming bytes from Serial UART:
  pos = 0;
  if (mySerial.available()) {
    digitalWrite(D0, LOW);   // turn the LED on
    Serial.print("IR Data available");
    // read the incoming byte:
    while (mySerial.available()) {
      buffer[pos % MAX] = mySerial.read();
      pos += 1;
      if (!mySerial.available()) delayMicroseconds(1200);
    }
    Serial.print (" Bytes read: ");
    Serial.println (pos);

  }
  digitalWrite(D0, HIGH);   // turn the LED off
  // read success?
  return (pos > 0 && buffer[0] == 0x1B );
  }   

String hex(int b) {
  char hex_buffer[3];
  sprintf (hex_buffer, "%02X", b);
  return hex_buffer;
}


void get_HEX_buffer(int n) {
  SML_buffer = "";

  for (int i = 0; i < n; i++) {
    if (i % 16 == 0)
      SML_buffer += "\n";
    int b = buffer[i];
    SML_buffer += hex(b) + " ";
  }
}

void get_EEPROM() {
  /*
     read EEPROM data
  */
  EEPROM.begin(32);                           // reserve 32 Bit for EEPROM usage
  EEPROM.get(0, W_diff);
  if (W_diff > 100) {
    EEPROM.put(0, 10);
    EEPROM.commit();
  }
  EEPROM.get(1, verbose);
}


void HTMLresponseGet(WiFiClient client) {
  // Anfrage zurücksenden
  String html;
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/plain");
  client.println("");
  client.println("version:   \t" + String(version));
  client.println("meter:     \t" + meterNo);
  client.println("W_diff:    \t" + String(W_diff));
  client.println("err_client:\t" + String(err_client));
  client.println("err_srv:   \t" + String(err_srv));
  client.println("err_read:  \t" + String(err_read));

  client.println("verbose:   \t" + String(verbose));
  client.println("W:         \t" + String(W));
  client.println("kWh:       \t" + String(kWh));
  if (verbose) {
    client.println("SML_buffer:" + SML_buffer);
  }
}
void HTMLresponse(WiFiClient client) {
  // Anfrage zurücksenden
  String html;
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println("");
  client.println("<!DOCTYPE HTML>");
  client.println("<html>");
  client.println("<body bgcolor='#ffffdd'>");

  client.println("<h2>Welcome to 8266 WIFI smart_meter</h2>");
  client.println("<hr>");
  client.println("<center>");
  html = "<table border=1>";
  html += "<tr><th>variable</th><th>value</th><th>description</th></tr>";
  html += "<tr><td>meterNo</td><td>" + meterNo + "</td><td>my smart meter number</td></tr>";  html += "<tr><td>W_diff</td><td>" + String(W_diff) + "</td><td>min change required to report<br>";
  String IP = WiFi.localIP().toString().c_str();
  html += "<a href='http://" + IP + "/update?W_diff=15'>update to 15</a></td></tr>";
  html += "<tr><td>W</td><td>" + String(W) + "</td><td>current Watt</td></tr>";
  html += "<tr><td>1/10.000 kWh</td><td>" + String(kWh) + "</td><td>summary of kWh in 1/10.000 steps</td></tr>";
  html += "<tr><td>err_client</td><td>" + String(err_client) + "</td><td>error connect counter client</td></tr>";
  html += "<tr><td>err_srv</td><td>" + String(err_srv) + "</td><td>error connect counter server_8266</td></tr>";
  html += "<tr><td>err_read</td><td>" + String(err_read) + "</td><td>error counter for read_IR buffer</td></tr>";
  html += "<tr><td>verbose</td><td>" + String(verbose) + "</td><td>used to show SML_buffer aswell.<br>";
  html += "<a href='http://" + IP + "/update?verbose=1'>update verbose=1</a></td></tr>";
  html += "<tr><td>fingerprint sha-1</td><td>" + String(fingerprint) + "</td><td>used for ssh secure connect<br>";
  html += "<a href='http://" + IP + "/update?fingerprint=hexString</a></td></tr>";

  html += "<tr><td>get</td><td>data</td><td>get text/plain data<br>";
  html += "<a href='http://" + IP + "/get'> get</a></td></tr>";
  if (verbose) {
    html += "<tr><td>SML V1.04</td><td><pre>" + SML_buffer + "</pre></td><td>buffer read from IR smart meter</td></tr>";
  }
  html += "</table>";
  client.println(html);
  client.println("<hr>");
  client.println("created by R.LANG "+String(version));
  client.println("</body>");
  client.println("</html>");
  client.flush();
}



///////////////////////////////////////////////////////////////////

void setup() {
  digitalWrite(D0, HIGH);                   // turn the LED off
  digitalWrite(D4, HIGH);                   // turn the LED off
  
  pinMode(D0, OUTPUT);                      //  to control onboard LED
  pinMode(D4, OUTPUT);                      //  to control onboard LED
  get_EEPROM();
  
  mySerial.begin(9600);                     // to my IR Reader
  Serial.begin(57600);                      // to debug via COM/USB
  while (!Serial) {
    delay(100); // wait for serial port to connect. Needed for native USB port only
  }
  // Mit Wifi verbinden
  Serial.print("connect to: " + String(ssid));
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("not connected to "+String(ssid)+" "+String(password));
  }
  Serial.println("8266 WiFi is connected ");

  // Start des server_8266s
  server_8266.begin();
  Serial.println("server_8266 started");

  // Print the IP address
  Serial.print("my URL: http://");
  Serial.println(WiFi.localIP());
} 




///////////////////////////////////////////////////////////////////



void loop() {
     
  // read IR
  if (read_SML_buffer()) {        // read IR
    // process Buffer
    Serial.println("new SML buffer read");
    get_HEX_buffer(pos);
    // Serial.println(SML_buffer);

    if (get_W_kWh(pos) && abs(W - W_last) > W_diff) {
      putData();
    }
  }
  do_HTML_request();
}


Rolf LANG - Remsstr. 39 - 71384 Weinstadt | 10:09:11 up 29 days, 12:35, 1 user, load average: 0.14, 0.15, 0.14