Điều khiển thiết bị bằng WIFI hay Công tắc WIFI là một ứng dụng cơ bản nhất của Smart Home. Với Blynk chúng ta sẽ thiết kế một bộ công tắc Wifi dùng trong hệ sinh thái “Nhà Ngu”. Nếu bạn chưa biết Nhà Ngu là gì thì hãy đọc bài viết: Tự làm Smart Home mang tên Nhà Ngu
Trước tiên hãy cứ bắt đầu từ việc đơn giản nhất, đó là bật tắt thiết bị như thế nào đã.
Ok, bắt đầu nhé.
Tạo code với Blynk examples
Để chế tạo một công tắc wifi thông minh, chúng ta sẽ sử dụng các công cụ của Blynk Platform.
Blynk hỗ trợ chúng ta rất nhiều trong việc viết code, các bạn truy cập vào link: Examples.blynk.cc
Trong đó là các bài Code, cho từng loại Board và ứng dụng khác nhau.
Chúng ta truy cập vào link trên, Chọn Board là Node MCU, chọn Connection là ESP8266 WIFI, Auth Token cứ để trống, và chọn Exam là Blynk Blink.
Khi đó Blynk sẽ tự Gen ra một đoạn Code.
Các bạn nhấn vào Coppy Example sau đó mở Arduino lên và paste vào.
Đổi SSID là tên WIFI nhà bạn, PASS là mật khẩu WIFI nhà bạn.
Auth Token chính là mã ứng dụng của bạn để truy cập vào Blynk API. Cách lấy như sau:
Đăng nhập vào Blynk, nhấn New Project -> Đặt tên dự án, dòng chip Node MCU, kiểu kết nối là WIFI-> Nhấn Create, Blynk sẽ gửi mail về gmail đăng kí Blynk của bạn.
Coppy mã đó dán vào ô. Auth Token
Tool-Chọn Board Node MCU
Sau đó chọn Port và nạp như Arduino bình thường.
Điều khiển LED bằng App Blynk
Blynk hỗ trợ làm công tắc Wifi bằng các Widget, trong bài này chúng ta sử dụng Button Widget. Mỗi một widget sẽ phải mua bằng Enegy, nếu hết Enegy bạn phải bỏ tiền ra mua. Nhưng đừng lo Blynk hỗ trợ cho mỗi Account 1000 enegy, như vậy cũng thoải mái để tạo 1 chiếc công tắc wifi rồi.
Sau khi nạp code vào cho Node MCU, chúng ta mở App Blink
- Chọn Project vừa tạo. Nhân nút Play (Hình tam giác)
- Nếu Node MCU được config đúng, App sẽ báo Nha Ngu IOT online
- Nhấn nút hình vuông (Vị trí nút play vừa nãy) để trở về cửa sổ thiết kế. Nhấn nút cộng để vào Widget Box
- Chọn Button, nút nhấn sẽ được đưa ra ngoài màn hình. Nhấn vào hình nút nhấn để config
Trong phần config Button
- Thay đổi tên: Cong tac 1. MODE là SWITCH
- Phần OUTPUT, click vào PN chọn D4. Chính là con led có sẵn trên Node MCU
Nhấn OK và nhấn Play để chạy thử như sau:
Điều khiển LED bằng nút nhấn thực tế
Công tắc WIFI thì ngoài việc điều khiển trên app, thì phải điều khiển được bằng tay nữa. Chúng ta sẽ bắt đầu code phần điều khiển bằng tay nhé.
Chúng ta sẽ sử dụng nút nhấn có sẵn trên Node MCU để điều khiển con LED D4. Trong Pinout Node MCU ta thấy Button đó là D3 (Flash)
Đầu tiên, định ngh ĩa D3 là Button, D4 là LED, một biến trạng thái LED là Status = 1 (LED tắt)
Trong hàm setup chọn pinmode và ghi giá trị ban đầu cho LED
Trong loop() chúng ta kiếm tra nút nhấn Button xem đã được nhấn chưa, Nếu được nhấn thì đảo trạng thái Status và ghi vào LED D4
Nạp chương trình và test
Vậy là ta đã có thể điều khiển được thiết bị qua cả App và nút nhấn thực tế, nhưng có một lỗi phát sinh đó là. Khi ta thay đổi trạng thái LED bằng nút nhấn, trên App sẽ không biết là chúng ta thay đổi, nên không cập nhật leed App. Điều này làm việc điều khiển trở nên ngu ngu, vì lâu lâu lại phải ấn 2 lần trên App mới chuyển được trạng thái.
Vậy sửa lỗi này như thế nào. Ta đến phần sau nhé
Đồng bộ nút nhấn và App trên công tắc WIFI
Một chiếc công tắc thông minh thì phải giao tiếp được giữa người và thiết bị. Khi con người nhấn nút trên App thì mạch phải phản hồi và ngược lại, khi con người nhấn nút nhấn trên mạch thì App cũng phải cập nhật. Việc giao tiếp qua lại đó gọi là đồng bộ.
Nguyên lý làm việc như sau:
App và Node MCU sẽ giao tiếp với nhau thông qua 1 cổng Vitual (Một luồng dữ liệu)
Khi cổng Vitual này thay đổi giá trị => Mạch sẽ cập nhật và thay đổi giá trị vào D4 (LED) => Khi mạch ghi giá trị lên cổng Vitual, trên App cũng sẽ thay đổi trạng thái
Đầu tiên, ta phải sửa Button trên App là 1 cổng Vitual thay vì D4.
Thêm 2 biến điều khiển. Thêm các câu lệnh như sau:
Trong đó biến:
- VIRTUAL_PIN_0 dùng để đọc dữ liệu từ cổng V0
- isPushOnApp: là biến kiểm tra nút nhấn trên App có được ấn hay không
- HBLYNK_CONNECTED: là hàm thực thi lệnh đồng bộ tất cả trạng thái trên App và mạch, khi chúng kết nối với nhau
- BLYNK_WRITE(V0): Hàm thực thi khi App ghi dữ liệu vào V0. Ghi giá trị V0 vào biến VIRTUAL_PIN_0. Thay đổi giá trị isPushOnApp = true để báo rằng có nút nhấn được nhấn.
Trong loop. Chúng ta thực hiện như sau
Khi trên App được nhấn, ta ghi giá trị của V0 lên LED
Khi nút nhấn trên Board được nhấn, ta ghi giá trị lên LED và Ghi giá trị lên V0 trả về App.
Nạp chương trình và chạy thử.
Config wifi không cần code, sử dụng wifimanager
Làm sao có thể kết nối wifi bằng điện thoại mà ko cần phải sửa code. Bạn có thấy các sản phẩm Công tăc WIFI như Sonoff, Xaomi, Tuya … người ta phải nạp code ko? không hề nhé.
Vậy làm như thế nào để chúng ta có thể làm vậy. Có 2 cách ESP8266 hỗ trợ đó là:
Với smart config chúng ta cần phải tự viết App rồi thêm code smartconfig vào, nhưng dự án này dùng Blynk, và Blynk V1 này không hỗ trợ làm việc đó. Vậy nên chúng ta sẽ sử dụng phương án 2, AP config.
AP config là gì?
AP hay Accesspoint là một điểm truy cập wifi, nghĩa là chúng ta sẽ biến ESP8266 thành 1 điểm truy cập wifi. Việc tiếp theo là dùng smartphone, kết nối vào mạng wifi đó, sau đó là điền thông tin vào và truyền ngược lại ESP8266.
Khi nhận được dữ liệu truyền về, ESP8266 sẽ thoát khỏi AP mode và vào lại chế độ Station mode và truy cập vào wifi nhà bạn như bình thường.’
Vậy để làm việc đó một cách đơn giản chúng ta sẽ sử dụng thư viện wifimanager
Cài đặt Wifimanager và ArduinoJson
Để download wifimanager chúng ta vào link: https://github.com/tzapu/WiFiManager
Tiếp đó chúng ta download thư viện ArduinoJson, nhớ chọn V5.0.0 nhé vì nó mới tương thích với thư viện Wifimanager
Clone hoặc Down zip về, sau đó cài đặt vào thư viện Arduino. Xem hướng dẫn tại đây: Cài thư viện Arduino
Lập trình Wifi Config
Tiếp tục, để lập trình wifi config ta sẽ follow theo các bước sau:
- Khi khởi động, đọc dữ liệu được ghi sẵn trong bộ nhớ, nếu có dữ liệu, dùng nó để login vào Blynk.
- Nếu không có dữ liệu, nhảy vào ap mode
- Nhập thông tin wifi và blynk token
- Kết nối với wifi và blynk token đó
Một số lưu ý khi lập trình
- Khi wifi được kết nối với một mạng, nó sẽ được tự động lưu lại, vì thế bạn không cần lưu wifi và mật khẩu vào bộ nhớ
- Để xóa wifi được lưu chúng ta dùng lệnh
WiFi.disconnect();
- Để xóa tất cả các file trong bộ nhớ dùng lệnh
SPIFFS.format();
Đầu tiên, thêm các thư viên
Khởi tạo hàm tick để nháy led, báo cho user biết đang trong trạng thái nào. Sử dụng Ticker để để set thời gian nháy bằng hàm: ticker.attach(time, tick);
- Nháy chậm 0.6s: Đang đọc bộ nhớ và phát wifi bằng AP mode
- Nháy nhanh 0.2s: Đang ở AP mode, điện thoại đang kết nối với Wifi phát ra
- Bật: Kết nối Blynk thành công
- Tắt: Kết nối với Blynk thất bại
Khởi tạo các hàm callback khi vào chế độ AP mode
Hàm reset xóa bộ nhớ và ngắt kết nối với wifi cũ
Khởi tạo các nguyên mẫu hàm để sử dụng.
Kiểm tra trong bộ nhớ có file config.json hay không, nếu có thì parse ra chuỗi và copy ra biến blynk_token
Setup Wifimanager, với blynk_token được truyền vào custom_blynk_token. Nếu thiết bị đã được connect với wifi, nó sẽ kết nối với wifi đó và sử dụng blynk_token để truy cập vào Blynk server.
Nếu chưa nhập wifi, sẽ tự động chuyển qua AP mode, với Tên Wifi là Nhà Ngu Công Tắc 1 và pass là 123456789.
Nếu nhập tất cả đúng sẽ connected được, nếu nhập sai wifi và pass ESP sẽ tự reset.
Sau khi connect thành công, lưu lại blynk_token vào bộ nhớ Flash bằng SPIFFS
Tiếp tới connect với Blynk server, nếu connect đúng API led sẽ sáng và hoạt động được luôn. Nếu sai token, đèn sẽ tắt.
Để kết nối với mạng wifi khác hoặc app blynk khác,cần xóa wifi và bynk_token, nhấn nút Reset (Nút flash trên kit) ESP sẽ format dữ liệu trong flash và disconnect wifi hiện tại.
Hướng dẫn setup wifi
Đầu tiên khi bật lên led sẽ nháy chậm, các bạn connect vào wifi mà ESP phát ra, gõ mật khẩu.
Tiếp tới mở trình duyệt, gõ 192.168.4.1 nếu không tự động được bật lên
Nhập wifi, mật khẩu nhà bạn và mã auth blynk token trong app.
Nhấn kết nối, nếu thành công led sẽ luôn sáng, nếu thất bại led sẽ tắt
Chuẩn bị phần cứng và Source Code
Ở đây mình sử dụng phím touch pad 5k mua ở shopee, với một cái mặt kính công tắc wifi cũng shopee
Nút nhấn ttp các bạn hàn nối điểm A để cho nó ở chế độ nhấn nhả (push) nhé, nếu để mặc định sẽ là kiểu switch.
Sau đó dùng băng keo dán vào sau của mặt kính.
Tiếp tới để điều khiển đèn, chúng ta sử dụng module Relay 5V
Cuối cùng là 1 cái nguồn 5V bất kì như sạc điện thoại, pin….
Kết nối nút nhấn với D1, D2, D5 relay với D6, D7, D8. D3 là nút reset mạng và D4 là led trên mạch
Nạp code sau đây và xem kết quả
#include <Arduino.h> #define BLYNK_PRINT Serial #include <ESP8266WiFi.h> #include <BlynkSimpleEsp8266.h> #include <DNSServer.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> //https://github.com/tzapu/WiFiManager #include <Ticker.h> #include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson #define BUTTON_RS D3 // reset #define LED D4 // led #define BUTTON1 D1 #define BUTTON2 D2 #define BUTTON3 D5 #define Den1 D6 #define Den2 D7 #define Den3 D8 Ticker ticker; char blynk_token[34] = ""; bool shouldSaveConfig = false; boolean checkData = false; //define vitual pin value int VIRTUAL_PIN_0; int VIRTUAL_PIN_1; int VIRTUAL_PIN_2; int VIRTUAL_PIN_3; int OLD_VIRTUAL_PIN_0; int OLD_VIRTUAL_PIN_1; int OLD_VIRTUAL_PIN_2; int OLD_VIRTUAL_PIN_3; //synching app BLYNK_CONNECTED() { Blynk.syncAll(); } BLYNK_WRITE(V0) { VIRTUAL_PIN_0 = param.asInt(); checkData = true; } BLYNK_WRITE(V1) { VIRTUAL_PIN_1 = param.asInt(); checkData = true; } BLYNK_WRITE(V2) { VIRTUAL_PIN_2 = param.asInt(); checkData = true; } BLYNK_WRITE(V3) { VIRTUAL_PIN_3 = param.asInt(); checkData = true; } //prototype function void tick(); void configModeCallback (WiFiManager *myWiFiManager); void saveConfigCallback (); void clearConfigToken(void); //begin program void setup() { Serial.begin(9600); pinMode(BUILTIN_LED, OUTPUT); ticker.attach(0.6, tick); //load config, if new start config Serial.println("mounting FS..."); if (SPIFFS.begin()) { Serial.println("mounted file system"); if (SPIFFS.exists("/config.json")) { //file exists, reading and loading Serial.println("reading config file"); 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"); strcpy(blynk_token, json["blynk_token"]); } else { Serial.println("failed to load json config"); } configFile.close(); } } } else { Serial.println("failed to mount FS, no config"); } //setup wifi manager WiFiManagerParameter custom_blynk_token("blynk", "Blynk Token", blynk_token, 32); WiFiManager wifiManager; wifiManager.setSaveConfigCallback(saveConfigCallback); wifiManager.addParameter(&custom_blynk_token); //Turn on AP mode wifiManager.setAPCallback(configModeCallback); if (!wifiManager.autoConnect("Nha Ngu Cong tac 1","123456789")) { Serial.println("failed to connect and hit timeout"); //reset and try agai ESP.reset(); delay(1000); } //if you get here you have connected to the WiFi Serial.println("connected...yeey :)"); ticker.detach(); //turn off led blink strcpy(blynk_token, custom_blynk_token.getValue()); //if the first time, save the config if (shouldSaveConfig) { Serial.println("saving config"); 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"); } json.printTo(Serial); json.printTo(configFile); configFile.close(); //end save } Serial.println("local ip"); Serial.println(WiFi.localIP()); //Set up button and light pinMode(Den1, OUTPUT); pinMode(Den2, OUTPUT); pinMode(Den3, OUTPUT); pinMode(BUTTON1, INPUT_PULLUP); pinMode(BUTTON2, INPUT_PULLUP); pinMode(BUTTON3, INPUT_PULLUP); pinMode(BUTTON_RS, INPUT_PULLUP); //Connect to Blynk Serial.println("BLYNK Connecting..."); Blynk.config(blynk_token); bool result = Blynk.connect(); if (result != true) { Serial.println("BLYNK Connection Fail"); digitalWrite(BUILTIN_LED, HIGH); } else { Serial.println("BLYNK Connected"); digitalWrite(BUILTIN_LED, LOW); } //set giá trị ban đầu cho V pin OLD_VIRTUAL_PIN_0 = VIRTUAL_PIN_0; OLD_VIRTUAL_PIN_1 = VIRTUAL_PIN_1; OLD_VIRTUAL_PIN_2 = VIRTUAL_PIN_2; OLD_VIRTUAL_PIN_3 = VIRTUAL_PIN_3; } void loop() { Blynk.run(); if (checkData == true) { if(VIRTUAL_PIN_3 != OLD_VIRTUAL_PIN_3) { Serial.println("Nhan tren APP V3:" + String(VIRTUAL_PIN_3)); VIRTUAL_PIN_0 = 1; VIRTUAL_PIN_1 = 1; VIRTUAL_PIN_2 = 1; Blynk.virtualWrite(V0, VIRTUAL_PIN_0); Blynk.virtualWrite(V1, VIRTUAL_PIN_1); Blynk.virtualWrite(V2, VIRTUAL_PIN_2); OLD_VIRTUAL_PIN_3 = VIRTUAL_PIN_3; } if(VIRTUAL_PIN_0 != OLD_VIRTUAL_PIN_0) { Serial.println("Nhan tren APP V0:" + String(VIRTUAL_PIN_0)); digitalWrite(Den1, VIRTUAL_PIN_0); OLD_VIRTUAL_PIN_0 = VIRTUAL_PIN_0; } if(VIRTUAL_PIN_1 != OLD_VIRTUAL_PIN_1) { Serial.println("Nhan tren APP V1:" + String(VIRTUAL_PIN_1)); digitalWrite(Den2, VIRTUAL_PIN_1); OLD_VIRTUAL_PIN_1 = VIRTUAL_PIN_1; } if(VIRTUAL_PIN_2 != OLD_VIRTUAL_PIN_2) { Serial.println("Nhan tren APP V2:" + String(VIRTUAL_PIN_2)); digitalWrite(Den3, VIRTUAL_PIN_2); OLD_VIRTUAL_PIN_2 = VIRTUAL_PIN_2; } checkData == false; } if (digitalRead(BUTTON1) == 0) { delay(100); if (digitalRead(BUTTON1) != 0) { VIRTUAL_PIN_0 = !VIRTUAL_PIN_0; Blynk.virtualWrite(V0, VIRTUAL_PIN_0); digitalWrite(Den1, VIRTUAL_PIN_0); Serial.println("Nhan tai Board V0:" + String(VIRTUAL_PIN_0)); } } if (digitalRead(BUTTON2) == 0) { delay(100); if (digitalRead(BUTTON2) != 0) { VIRTUAL_PIN_1 = !VIRTUAL_PIN_1; Blynk.virtualWrite(V1, VIRTUAL_PIN_1); digitalWrite(Den2, VIRTUAL_PIN_1); Serial.println("Nhan tai Board V1:" + String(VIRTUAL_PIN_1)); } } if (digitalRead(BUTTON3) == 0) { delay(100); if (digitalRead(BUTTON3) != 0) { VIRTUAL_PIN_2 = !VIRTUAL_PIN_2; Blynk.virtualWrite(V2, VIRTUAL_PIN_2); digitalWrite(Den3, VIRTUAL_PIN_2); Serial.println("Nhan tai Board V2:" + String(VIRTUAL_PIN_2)); } } if (digitalRead(BUTTON_RS) == 0) { delay(100); if (digitalRead(BUTTON_RS) != 0) { Serial.println("Wifi reset setting"); SPIFFS.format(); WiFi.disconnect(); delay(1000); ESP.reset(); delay(2000); } } } void tick() { int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state } //gets called when WiFiManager enters configuration mode void configModeCallback (WiFiManager *myWiFiManager) { Serial.println("Entered config mode"); Serial.println(WiFi.softAPIP()); //if you used auto generated SSID, print it Serial.println(myWiFiManager->getConfigPortalSSID()); //entered config mode, make led toggle faster ticker.attach(0.2, tick); } //gets called when save config void saveConfigCallback () { Serial.println("Should save config"); shouldSaveConfig = true; } void clearConfigToken(void) { Serial.println("Clear Token"); DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.createObject(); json["blynk_token"] = "Blynk_Token"; File configFile = SPIFFS.open("/config.json", "w"); Serial.println(); if (!configFile) { Serial.println("failed to open config file for writing"); } json.printTo(Serial); json.printTo(configFile); configFile.close(); }
Kết quả
Vậy là chúng ta đã hoàn thành sản phẩm đầu tiên trong dự án Nhà Ngu IOT, đây là một sản phẩm có thể sử dụng ngoài thực tế, trong gia đình bạn hàng ngày. Thế nhưng vẫn còn 1 số vấn đề như: làm sao để update firmware từ xa (FOTA) cho thiết bị, làm sao để kết nối nó với các môi trường khác để điều khiển qua giọng nói hay lập kịch bản bật tắt đèn.
Tất cả sẽ có trong các phần tiếp theo của dự án Nhà Ngu. Các bạn đón đọc nhé
Đừng quên like và chia sẻ bài viết này tới bạn bè. Và tham gia nhóm Nghiện lập trình để giao lưu và kết bạn với những anh em cùng đam mê.
Theo dõi fanpage của mình tại: https://www.facebook.com/khuenguyencreator
Kênh Youtube: https://www.youtube.com/channel/UCt8cFnPOaHrQXWmVkk-lfvg