#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¶m=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);
}