สวัสดีครับสำหรับโพสต์นี้ผมจะทำโปรเจคเกี่ยวกับการวัดค่าพลังงานไฟฟ้าบวกกับค่าใช้จ่ายต่อเดือนที่บ้านด้วย Pzem 004t v3.0 + esp 8266+ LCD 2004+Blynk+ … ซึ่งก็มีหลายท่านนะครับที่ทำตัวนี้อยู่แล้ว ถ้ามีอะไรแนะนำก็ขอความกรุณาด้วยนะครับจะได้นำไปวิเคราะห์และเพิ่มเติมเข้าไปในโปรเจคของผม สาเหตุที่ตั้งกระทู้ใหม่ก็เพราะว่าผมต้องการที่จะค้นหาข้อมูลในกรณีที่ต้องกลับมาทบทวนซ้ำอีกครั้ง ดังนั้นจึงไม่ได้เอาไปนำเสนอไว้ในโพสต์อื่นๆ สำหรับตัว Project นี้ถ้ามีเวลาผมจะมาลงรายละเอียดอีกทีนะครับ ตอนนี้ถ้าใครอยากดูคลิปที่ผมทำก็ชมคลิปกันไปก่อน การพูดอาจจะตะกุกตะกักหน่อย เพราะไม่ได้คุยกับคนมาระยะหนึ่งแล้ว อิ…อิ…แต่บอกไว้ก่อนนะครับว่าผมยังต้องมีส่วนที่เพิ่มเติมอีก
หรือท่านใดที่ชมคลิปแล้ว คิดว่าแค่นี้พอแล้ว ก็ลอง Code ดูครับ แก้ไข 01/03/2563 23:42
- รับค่าข้อความจาก Blynk เข้ามา เก็บในรูปแบบ String /แปลงค่าจาก String เป็น Float/เขียนค่า ที่เป็น Float ลงใน EEPROM Address 0 /ใช้ในกรณีที่ ไม่ได้เชื่อมต่อกับ App Blynk ก็ยังสามารถดูค่าทั้งหมดได่้อย่างถูกต้อง แต่ทุกครั้งที่มีการเปลี่ยนค่า Ft จะต้องถูกกำหนดค่าผ่าน App Blynk มาทุกครั้ง
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <FS.h>
#include <DNSServer.h>
#include <ArduinoJson.h>
//------------------------------------------------------------------------------------------------------------------------//
#include <BlynkSimpleEsp8266.h>
//------------------------------------------------------------------------------------------------------------------------//
#include <EEPROM.h>
//------------------------------ การกำหนดค่าในส่วนของ Blynk ------------------------------------------------//
#define BLYNK_DEBUG
#define BLYNK_PRINT Serial
int blynkIsDownCount = 0;
char blynk_token[34] = "8dwBaSBOE8htLFrK37JPAYB1hgtm8TaQkU";//ใส่ Blynk_token ของเราที่ Blynk ส่งมาทาง Email ตอน Create Project ใหม่
BlynkTimer timer;
//---------------------------------- กำหนดหมายเลขของขาของ Node MCU ESP 8266 --------------------------------------------//
#define D0 16 // ใช้ไฟ LED สีฟ้า ของบอร์ด MCU ESP8266 ให้มีสัญญาณไฟกระพริบ ตาม Code ที่เขียน
#define D3 0 // ใช้เป็นปุ่มกด เพื่อเข้า AP Config ได้ตามความต้องการของผู้ใช้ + เป็นปุ่ม Reset Energy (kWh)
//------------------------------------------------------------------------------------------------------------------------//
#include <Scheduler.h>
// ------------------------โหลดไลบารี่ของจอ LCD มาใช้งาน โดยแบบนี้จะสามารถ Create Characterได้ ---------------------------------//
#include <LCD.h> // บรรทัดที่ 1 และ 2 สามารถสลับบรรทัดกันได้
#include <Wire.h> // บรรทัดที่ 1 และ 2 สามารถสลับบรรทัดกันได้
#include <LiquidCrystal_I2C.h> //บรรทัดนี้ต้องอยู่ในบรรทัดที่ 3 เท่านั้น ถ้าไปอยู่บรรทัดที่ 1 และ 2 จะคอมไพล์ไม่ผ่าน
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); //// ตั้งค่า Address ของอุปกรณ์จอ LCD ที่มีการเชื่อมต่อแบบ I2C
//------------------------------------------------------------------------------------------------------------------------//
bool shouldSaveConfig = false;
//callback notifying us of the need to save config
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
//------------------------------------------------------------------------------------------------------------------------//
//-------------สำหรับ Server local ที่แจกให้ เพิ่ม **** แค่ 2 บรรทัดนี้-------------- (ถ้าเป็น Blynk Server ปกติไม่ต้องใส่) ----------//
char server[] = "oasiskit.com";
int port = 8080;
//------------------------------------------------------------------------------------------------------------------------//
#include <PZEM004Tv30.h>
PZEM004Tv30 pzem(12, 13); // 12(D6)=RX , 13(D7) = TX : TX->D6(GPIO012),RX->D7(GPIO013)
//ฉันเพิ่งเปลี่ยน PZEM004T.h บรรทัด 34 (#define PZEM_DEFAULT_ADDR 0x0F เป็น 0x001)ที่บ้าน
float Ft;
String Ft_1;
float Ft_2;
float Electric_bill;
float kWh_1;
float kWh_2;
float kWh_3;
float kWh_T;
float kWh_T1;
int Blynkreset_kWh;
WidgetLED led_rst_kWh(V12);//จะ ON เมื่อกดปุ่มค้างไว้ 5 วินาที
BLYNK_WRITE(V10)
{
Ft_1 = param.asString();//รับค่าข้อความจาก Blynk เข้ามา เก็บในรูปแบบ String
Ft_2 = Ft_1.toFloat();//แปลงค่าจาก String เป็น Float
//เราป้อนค่า จาก Blynk เป็น String = -0.116 ให้ Ft_1 รับค่ามา
//Ft จะรับค่าการแปลง จาก Ft_1 เป็น Float = -0.12
//จะเห็นว่า ค่า Folat จะปัดค่าเป็นจุดทศนิยม ไปเป็น 2 ตำแหน่งจะไม่มากกว่านี้เนื่องด้วยความสามารถตัวของอุปกรณ์เอง
//ทำให้ค่าเพี้ยนไป ประมาณ 3.44% ทำให้ค่าไฟ/เดือน น้อยกว่าค่าจริงไป 3.44% ประมาณ 0.64(150หน่วย) - 2.14(500หน่วย) บาท
//โปรเจคนี้จะไม่เปลี่ยนค่า ใดๆ เพราะค่าต่างกันเล็กน้อย (1 เดือน ใช้ไฟไม่ถึง 500 หน่วยแน่นอน)
//ชุดนี้ทำขึ้นในกรณีที่ ไม่ได้เชื่อมต่อกับ App Blynk ก็ยังสามารถดูค่าทั้งหมดได่อย่างถูกต้อง แต่ทุกครั้งที่มีการเปลี่ยนค่า Ft จะต้องถูกกำนดค่าผ่าน App Blynk มาทุกครั้ง
EEPROM.put(0, Ft_2); //เขียนค่า Ft_2 ที่เป็น Float ลงใน Address 0
EEPROM.commit(); //จบการเขียน
Ft = EEPROM.get(0, Ft_2);//อ่าน ค่า Ft_2 ที่เป็น Float จาก Address 0
Serial.println(Ft);//แสดงค่า Ft_2 ใน Serial Monitor
lcd.clear();
delay(200);
}
BLYNK_WRITE(V11)//Reset kWh ภายใน 5 วินาที
{
int pinValue = param.asInt();
if (pinValue == 1) {
Blynkreset_kWh = 1;
}
if (pinValue == 0) {
Blynkreset_kWh = 0;
}
}
class Task_1 : public Task {
protected:
void setup() { }
void loop() {
//-----------------------------------------------------------------------------------------------------------//
if (Blynk.connected())
{
Blynk.run();
digitalWrite(D0, LOW);
} else {
digitalWrite(D0, LOW);
delay(200);
digitalWrite(D0, HIGH);
delay(100);
}
//-----------------------------------------------------------------------------------------------------------//
timer.run();//ให้เวลาของ Blynk ทำงาน
delay(1000);
}
} Task_1_task;
class Task_2 : public Task {
protected:
void setup() { }
void loop() {
// Serial.println(Ft_1);
// Serial.print("Ft:");
// Serial.println(Ft);
//
// Serial.print("Ft:");
// Serial.println(Ft, 3);
float voltage = pzem.voltage();
if (voltage != NAN) {
//Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V");
lcd.setCursor(0, 0);
lcd.print("V: ");
lcd.setCursor(3, 0);
lcd.print(voltage, 2);
Blynk.virtualWrite(V1, voltage);
} else {
//Serial.println("Error reading voltage");
// lcd.setCursor(0, 0);
// lcd.print((char)0x20);
// lcd.print((char)0x20);
}
float current = pzem.current();
if (current != NAN) {
//Serial.print("Current: "); Serial.print(current); Serial.println("A");
lcd.setCursor(12, 0);
lcd.print("A: ");
lcd.setCursor(15, 0);
lcd.print(current, 2);
Blynk.virtualWrite(V2, current);
} else {
//Serial.println("Error reading current");
// lcd.setCursor(15, 0);
// lcd.print((char)0x20);
// lcd.print((char)0x20);
}
float power = pzem.power();
if (current != NAN) {
//Serial.print("Power: "); Serial.print(power); Serial.println("W");
lcd.setCursor(0, 1);
lcd.print("W: ");
lcd.setCursor(3, 1);
lcd.print(power, 2);
Blynk.virtualWrite(V3, power);
} else {
//Serial.println("Error reading power");
// lcd.setCursor(3, 1);
// lcd.print((char)0x20);
// lcd.print((char)0x20);
}
float energy = pzem.energy();
if (current != NAN) {
//Serial.print("Energy: "); Serial.print(energy, 3); Serial.println("kWh");
lcd.setCursor(10, 1);
lcd.print("kWh: ");
lcd.setCursor(15, 1);
lcd.print(energy, 2);
Blynk.virtualWrite(V4, energy);
} else {
//Serial.println("Error reading energy");
// lcd.setCursor(15, 1);
// lcd.print((char)0x20);
// lcd.print((char)0x20);
}
float frequency = pzem.frequency();
if (current != NAN) {
//Serial.print("Frequency: "); Serial.print(frequency, 1); Serial.println("Hz");
lcd.setCursor(0, 2);
lcd.print("Hz: ");
lcd.setCursor(3, 2);
lcd.print(frequency, 1);
Blynk.virtualWrite(V5, frequency);
} else {
//Serial.println("Error reading frequency");
// lcd.setCursor(3, 2);
// lcd.print((char)0x20);
// lcd.print((char)0x20);
}
float pf = pzem.pf();
if (current != NAN) {
//Serial.print("PF: "); Serial.println(pf);
lcd.setCursor(11, 2);
lcd.print("PF: ");
lcd.setCursor(15, 2);
lcd.print(pf, 2);
Blynk.virtualWrite(V6, pf);
} else {
//Serial.println("Error reading power factor");
// lcd.setCursor(15, 2);
// lcd.print((char)0x20);
// lcd.print((char)0x20);
}
//Serial.println();
delay(2000);
}//loop
} Task_2_task;
class Task_3 : public Task {
protected:
void setup() { }
void loop() {
float energy = pzem.energy();
// กดปุ่มที่หน้าเครื่อง 5 วินาที เพื่อ Reset kWh
if (digitalRead(D3) == LOW) {
delay(5000);
pzem.resetEnergy();
}
// กดปุ่มจาก Blynk 5 วินาที เพื่อ Reset kWh กดค้างจนกว่า LED จะดับ
if (Blynkreset_kWh == 1) {
led_rst_kWh.on();
delay(5000);
pzem.resetEnergy();
led_rst_kWh.off();
}
if (Blynkreset_kWh == 0) {
led_rst_kWh.off();
}
//1.2 อัตราปกติปริมาณการใช้พลังงานไฟฟ้าเกินกว่า 150 หน่วยต่อเดือน
//ที่บ้านเราใช้ค่านี้ ประเภท 1125 คือ ใช้ไฟฟ้าเกิน 150 หน่วย/เดือน อัตราค่าบริการ 38.22 บาท/เดือน
//1 - 150 หน่วยละ 3.2484 บาท
if (energy >= 1 && energy <= 150) {
kWh_1 = energy * 3.2484;
}
if (energy > 150) {
kWh_1 = 150 * 3.2484;
}
//หน่วยที่ 151 - 400 หน่วยละ 4.2218 บาท
if (energy >= 151 && energy <= 400) {
kWh_2 = energy * 4.2218;
}
if (energy > 400) {
kWh_2 = 250 * 4.2218;
//401 -- > หน่วยละ 4.4217 บาท
kWh_3 = (energy-400) * 4.4217;
}
//รวมค่าใช้จ่ายทั้งหมด
kWh_T = kWh_1 + kWh_2 + kWh_3;
kWh_T1 = kWh_T + 38.22 + (energy * Ft);
Electric_bill = kWh_T1 + (kWh_T1 * 0.07);
lcd.setCursor(0, 3);
lcd.print("Bill/M: ");
lcd.setCursor(8, 3);
lcd.print(Electric_bill , 2);
lcd.setCursor(15, 3);
lcd.print("Bath");
Blynk.virtualWrite(V7, Electric_bill);
delay(2000);
}//loop
} Task_3_task;
//------------------------------------------------------------------------------------------------------------------------//
//********************************************* void setup **************************************************//
//------------------------------------------------------------------------------------------------------------------------//
void setup() {
//-------IO NODE MCU Esp8266-------//
pinMode(D0, OUTPUT); //กำหนดโหมดใช้งานให้กับขา D0 เป็นขา สัญญาณไฟ ในสภาวะต่างๆ
pinMode(D3, INPUT_PULLUP);//กำหนดโหมดใช้งานให้กับขา D3 เป็นขา กดปุ่ม ค้าง เพื่อตั้งค่า AP config
// ให้ LED ทั้งหมดดับก่อน
digitalWrite(D0, LOW);//ให้หลอด LED สีฟ้าดับก่อน
//-------------------------------//
Serial.begin(115200);
//-------------------------------//
EEPROM.begin(512);
//ต้องการให้รู้ว่า ค่า Ft ที่เป็น Float ที่เก็บใน EEPROM มีค่า = ค่าที่ถูกป้อนมาจาก Bylnk จริงๆ
Ft = EEPROM.get(0, Ft_2);
Serial.print("Ft in Void setup : ");
Serial.println( Ft);
lcd.clear();
lcd.begin(20, 4);
lcd.clear();
//read configuration from FS json
Serial.println("mounting FS...");//แสดงข้อความใน Serial Monitor
if (SPIFFS.begin()) {
Serial.println("mounted file system");//แสดงข้อความใน Serial Monitor
if (SPIFFS.exists("/config.json")) {
//file exists, reading and loading
Serial.println("reading config file");//แสดงข้อความใน Serial Monitor
File configFile = SPIFFS.open("/config.json", "r");
if (configFile) {
Serial.println("opened config file");
size_t size = configFile.size();
// Allocate a buffer to store contents of the file.
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.parseObject(buf.get());
json.printTo(Serial);
if (json.success()) {
Serial.println("\nparsed json");//แสดงข้อความใน Serial Monitor
strcpy(blynk_token, json["blynk_token"]);
} else {
Serial.println("failed to load json config");//แสดงข้อความใน Serial Monitor
}
}
}
} else {
Serial.println("failed to mount FS");//แสดงข้อความใน Serial Monitor
}
//end read
//************************** AP AUTO CONNECT ********************************************//
WiFiManagerParameter custom_blynk_token("blynk", "blynk token", blynk_token, 34);
//WiFiManager
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);
wifiManager.addParameter(&custom_blynk_token);
lcd.clear();
lcd.setCursor(0, 0);//กำหนดตำแหน่งที่0 ของแถวที่ 0 (อยู่ข้างบนสุดของจอ LCD)
lcd.print("Wait 5 Sec AP Config"); //แถวที่ 0 ตำแหน่งที่ 0 ให้แสดงตัวอักษร
for (int i = 5; i > -1; i--) { // นับเวลาถอยหลัง 5 วินาทีก่อนกดปุ่ม AP Config
digitalWrite(D0, HIGH);
delay(500);
digitalWrite(D0, LOW);
delay(500);
Serial.print (String(i) + " ");//แสดงข้อความใน Serial Monitor
lcd.setCursor(10, 1);
lcd.print(String(i) + " ");
lcd.setCursor(0, 2);
lcd.print("Button Press->config");
}
if (digitalRead(D3) == LOW) {
digitalWrite(D0, HIGH);
Serial.println("Button Pressed");//แสดงข้อความใน Serial Monitor
lcd.clear();
lcd.setCursor(4, 0);
lcd.print("Button Pressed");
lcd.setCursor(0, 1);
lcd.print("Smartphone AP Config ");
wifiManager.resetSettings();//ให้ล้างค่า SSID และ Password ที่เคยบันทึกไว้
// wifiManager.autoConnect(); ใช้สร้างชื่อแอคเซสพอยต์อันโนมัติจาก ESP + ChipID
if (!wifiManager.autoConnect("PUYIOT PZEM")) {
Serial.println("failed to connect and hit timeout");//แสดงข้อความใน Serial Monitor
delay(3000);
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(5000);
}
}
Serial.println("Connected.......OK!)");//แสดงข้อความใน Serial Monitor
strcpy(blynk_token, custom_blynk_token.getValue());
lcd.clear();
lcd.setCursor(2, 2);
lcd.print("Connected....OK!");
delay(1000);
//save the custom parameters to FS
if (shouldSaveConfig) {
Serial.println("saving config");//แสดงข้อความใน Serial Monitor
lcd.setCursor(4, 1);
lcd.print("saving config");
delay(1000);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
json["blynk_token"] = blynk_token;
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
Serial.println("failed to open config file for writing");//แสดงข้อความใน Serial Monitor
lcd.setCursor(4, 1);
lcd.print("Failed> open config");
delay(1000);
}
json.printTo(Serial);
json.printTo(configFile);
configFile.close();
//end save
}
//************************** จบ AP AUTO CONNECT *****************************************//
Serial.println("local ip");
delay(100);
Serial.println(WiFi.localIP());
Serial.println("gateway");
delay(100);
Serial.println(WiFi.gatewayIP());
Serial.println("subnetMask");
delay(100);
Serial.println(WiFi.subnetMask());
Serial.println("SSID");
delay(100);
Serial.println(WiFi.SSID());
Serial.println("Password");
delay(100);
Serial.println(WiFi.psk());
Blynk.config(blynk_token, server, port);
////เริ่มการเชื่อมต่อ Blynk Server*********สำหรับ Server local ที่แจกให้ เพิ่ม, server, port แค่นี้จบ
timer.setInterval(30000L, reconnecting); //Function reconnect
timer.setInterval(30000L, reconnecting); //Function reconnect
//ตั้งเวลาส่งข้อมูลให้ Blynk Server ทุกๆ 30 วินาที
Scheduler.start(&Task_1_task);
Scheduler.start(&Task_2_task);
Scheduler.start(&Task_3_task);
Scheduler.begin();
}
//------------------------------------------------------------------------------------------------------------------------//
//********************************************* จบ void setup **************************************************//
//------------------------------------------------------------------------------------------------------------------------//
//------------------------------------------------------------------------------------------------------------------------//
//********************************************* void Loop ***************************************************//
//------------------------------------------------------------------------------------------------------------------------//
void loop() {
}
//------------------------------------------------------------------------------------------------------------------------//
//********************************************* จบ void Loop **************************************************//
//------------------------------------------------------------------------------------------------------------------------//
//------------------------------------------------------------------------------------------------------------------------//
void reconnecting()
{
if (!Blynk.connected())
{
blynkIsDownCount++;
BLYNK_LOG("blynk server is down! %d times", blynkIsDownCount);
Blynk.connect(5000);
}
}
BLYNK_CONNECTED()
{
Blynk.syncAll();//ให้ซิงค์ข้อมูลทั้งหมดล่าสุดจาก Blynk Server
}