"ผมไม่ได้ล้มเหลว ผมแค่พบ 10,000 วิธีที่ใช้การไม่ได้" - โทมัส เอ. เอดิสัน วันนี้ อิฐวิเศษในกระเป๋าน้องให้เข้าถึงข้อมูลทุกอย่างได้ พี่จะสอนให้น้องใช้พลังนั้นควบคุมโคมไฟสไตล์วินเทจด้วยเสียงตัวเอง ด้วยงบแค่ 300 บาท (ประมาณ $5) แค่รีโปรแกรม Sonoff relay กับสร้างแอพ Android ง่ายๆ น้องก็สร้างอนาคตด้วยมือตัวเองได้แล้ววันนี้เลย
มันสุดยอดมาก แต่เมื่อไม่นานมานี้ คนในโรงเรียนยังเขียนหนังสือด้วยขนนกเลย วันนี้เรามีอิฐวิเศษที่มีภาพเคลื่อนไหว ใส่กระเป๋าได้ และให้ข้อมูลทั้งโลกเข้าถึงได้ การควบคุมด้วยเสียง และที่คนพูดกันบ่อยๆ คือภัยคุกคามต่อมนุษยชาติจากปัญญาประดิษฐ์ เมื่อไม่นานมานี้ เทคโนโลยีพวกนี้ดูเหมือนเป็นแค่จินตนาการที่เอื้อมไม่ถึง แต่พี่จะแสดงให้ดูว่าทุกคนที่มีความรู้พื้นฐานด้านโปรแกรมมิ่ง สามารถใช้การควบคุมด้วยเสียงได้ยังไง เอาล่ะ เริ่มกันเลย
**คำเตือน!!!** งานนี้เกี่ยวกับไฟฟ้าแรงสูง! ผู้เขียนบทความไม่รับผิดชอบต่อการกระทำของน้องๆ นะ! หรือความเสียหายทางวัตถุ หรือร่างกายใดๆ ที่อาจเกิดขึ้นจากการใช้คำแนะนำนี้!

แผนภาพรวมของขั้นตอนการทำงานของอุปกรณ์

เนื่องจากโทรศัพท์ในเวลาเดียวกันสามารถเชื่อมต่อกับจุดเชื่อมต่อ Wi-Fi ได้แค่จุดเดียว เราจะทำงานผ่านเราเตอร์ (Router) โดยรวมแล้วมันสะดวกดีเวลาอุปกรณ์สมาร์ททั้งหมดของเราอยู่ในเครือข่ายเดียวกัน เราจะจัดการมันได้ง่ายโดยไม่ต้องคอยเชื่อมต่อใหม่กับแต่ละตัวตลอด ข้อเสียของวิธีนี้คือ อุปกรณ์ทั้งหมดของเราจะขึ้นอยู่กับประสิทธิภาพของเราเตอร์ตัวเดียว
1 - เราต้องรีโปรแกรม Sonoff Wi-Fi relays
โดยค่าเริ่มต้น มันถูกตั้งค่าให้ทำงานผ่านเซิร์ฟเวอร์ของจีน ในความเข้าใจของพี่ มันไม่ค่อยสะดวกเท่าไหร่ที่จะปิดหลอดไฟในห้องน้ำผ่านเซิร์ฟเวอร์จีน สำหรับขั้นตอนนี้เราต้องแยกชิ้นส่วนมันออกมาและบัดกรีขาสำหรับการรีโปรแกรม


ตอนนี้เราสามารถเชื่อมต่อโมดูล cp2102 จาก Silicon Labs กับ Sonoff wifi ได้แล้ว พี่ก็ใช้โมดูลนี้โปรแกรม Arduino mini ด้วย


**คำเตือน!!!** ตอนที่น้องกำลังรีโปรแกรมอยู่นะ อย่าเชื่อมต่อโมดูลเข้ากับไฟบ้าน 220/110 โวลต์เด็ดขาด! ห้ามช็อตนะตัวนี้
การรีโปรแกรมรีเลย์นี่ง่ายมาก มันคือโมดูล esp8266 ทั่วไปนี่แหละ พี่เอา sketch มาตรฐานของเซิร์ฟเวอร์ Access Point จาก Arduino IDE มาแล้วปรับนิดหน่อย
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiClient.h>
// name and password from WiFi network
const char* ssid = "Your access point (router) name";
const char* password = "router password";
IPAddress ip(192,168,1,112); // enter static ip
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
// Server port 80
WiFiServer server(80);
void setup() {
Serial.begin(115200);
delay(100);
//preparing GPIO
pinMode(12, OUTPUT);
digitalWrite(12, 1);
pinMode(13, OUTPUT);
digitalWrite(13, 1);
// connecting to WiFi
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
WiFi.config(ip, gateway, subnet);
// waiting for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
// run server
server.begin();
Serial.println("Server started");
// show ip
Serial.println(WiFi.localIP());
}
void loop() {
// connection check
WiFiClient client = server.available();
if (!client) {
return;
}
// Waiting for data
Serial.println("new client");
while (!client.available()) {
delay(1);
}
// Reading the first line of the query
String req = client.readStringUntil('\r');
Serial.println(req);
client.flush();
// works with GPIO
if (req.indexOf("/12/0") != -1)
digitalWrite(12, 0);
else if (req.indexOf("/12/1") != -1){
digitalWrite(12, 1);
Serial.println("TEST OK");
String s = "HTTP/1.1 200 OK\r\
Content-Type: text/html\r\
\r\
<!DOCTYPE HTML>\r\
<html>\r\
Test OK. Uptime: ";
// UpTime
int Sec = (millis() / 1000UL) % 60;
int Min = ((millis() / 1000UL) / 60UL) % 60;
int Hours = ((millis() / 1000UL) / 3600UL) % 24;
int Day = ((millis() / 1000UL) / 3600UL / 24UL);
s += Day;
s += "d ";
s += Hours;
s += ":";
s += Min;
s += ":";
s += Sec;
s += "</html>\
";
client.print(s);
client.stop();
return;
}
else
// If an invalid query write error
{
Serial.println("invalid request");
String s = "HTTP/1.1 200 OK\r\
Content-Type: text/html\r\
\r\
<!DOCTYPE HTML>\r\
<html>\r\
Invalid request";
s += "</html>\
";
client.print(s);
client.stop();
return;
}
client.flush();
// Response formation
String s = "HTTP/1.1 200 OK\r\
Content-Type: text/html\r\
\r\
<!DOCTYPE HTML>\r\
<html>\r\
GPIO set OK";
s += "</html>\
";
// Send the response to the client
client.print(s);
delay(1);
Serial.println("Client disonnected");
}
น้องต้องกำหนดชื่อไวไฟ (router) รหัสผ่าน และที่อยู่ IP แบบคงที่ (static ip) ให้กับหลอดไฟด้วยนะ ตัวรีเลย์ควบคุมอยู่ที่ขา (pin) 12 อย่าลืมตั้งค่า Flash size เป็น 1 MB ใน Arduino IDE ด้วยล่ะ
กดปุ่มค้างไว้บนบอร์ด Sonoff แล้วเสียบ cp2102 converter เข้า USB คอมพิวเตอร์ (ระหว่างที่กดปุ่มค้างไว้) รอสัก 2-3 วินาทีแล้วค่อยปล่อยปุ่ม ตอนนี้มันเข้าสู่โหมดแฟลชแล้ว จัดการโหลดสเก็ตช์ไฟล์ใน Arduino IDE แล้วกด Verify / Compile ได้เลย พอแฟลชเสร็จ โมดูลจะรีสตาร์ทและไฟ LED สีเขียวจะเริ่มกระพริบ
2 - มาทำแอปมือถือควบคุมรีเลย์ไวไฟกันดีกว่า (Android)
พี่จะให้แค่โค้ดส่วนสำคัญๆ นะ น้องเอาไปประยุกต์ใช้ในแอปของตัวเองได้ Google มีอินเตอร์เฟซสำหรับจดจำเสียง (voice recognition) ที่ใช้ง่ายโคตรๆ อยู่นะ เอาโค้ดนี้ไปใช้แปลงเสียงเป็นข้อความแล้วเก็บลงสตริงธรรมดาได้เลย
// Main code to start speech recognition.
// You can put it's execution, on some button in your app.
Intent speechIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
speechIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak please");
startActivityForResult(speechIntent, RESULT_SPEECH_TO_TEXT);
//------------------------------------------------------------------------
// Then on onAcivityResult we will get result
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RESULT_SPEECH_TO_TEXT && resultCode == RESULT_OK) {
ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if (!matches.isEmpty()) {
String result_recognition_string = matches.get(0);
// convert to lower case, since google speech recognition
// returns "different" results. For example: youtube defines //as YouTube
result_recognition_string = result_recognition_string.toLowerCase(Locale.getDefault());
if (result_recognition_string.contains("lamp on") ) {
// lamp on
new LightOn().execute();
}
}
}
}
จากนั้นน้องก็เอาไปเปรียบเทียบกับคำสั่งที่อยากให้มันทำงานได้เลย
ส่วนนี่คือโค้ดสำหรับทำงานกับ WiFi ส่งข้อความไปหาเซิร์ฟเวอร์
public class LightOn extends AsyncTask<Void,Void,Void> {
private static final String LOG_TAG = "MyLog";
@Override
protected Void doInBackground(Void... params) {
URL url;
HttpURLConnection urlConnection = null;
try {
url = new URL("http://192.168.1.112/12/1"); // "0" to turn off
urlConnection = (HttpURLConnection) url
.openConnection();
InputStream in = urlConnection.getInputStream();
InputStreamReader isw = new InputStreamReader(in);
int data = isw.read();
while (data != -1) {
char current = (char) data;
data = isw.read();
System.out.print(current);
Log.e(LOG_TAG, "Reply from server - " + current);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
}
// run it
new LightOn().execute();
มันง่ายมากเลยน้อง "1" คือเปิดไฟ "0" คือปิดไฟ น้องจะเพิ่มเงื่อนไขเช็คอะไรก็ได้ตามใจเลย หรือจะรับค่าจากเซิร์ฟเวอร์ก็จัดไป ตัวพี่เองทำโปรแกรมไว้สองแบบ แบบแรกคือวิดเจ็ตควบคุมด้วยเสียง เอาไว้วางบนเดสก์ท็อป เรียกใช้ได้ตลอด แบบที่สองคือแอปพลิเคชันกดปุ่มเปิด/ปิดไฟธรรมดาๆ



พี่ว่าน้องอ่านมาถึงตรงนี้คงเริ่มเบื่อแล้วล่ะ งั้นไปดูวิดีโอตัวอย่างการทำงานกันดีกว่า ว่ามันเป็นยังไง
อย่างที่ไอน์สไตน์ว่าไว้ "ขีดจำกัดของความเป็นไปได้ ขึ้นอยู่กับจินตนาการของเราเท่านั้น"
สนุกกับการสร้างสรรค์งาน แล้วก็ใช้สมองให้เต็มที่นะน้อง! สู้งาน