嗯… HamCQ 不支援上傳 .ino 格式的檔案。
大家可以在 Arduino IDE 中建立一個新的檔案,將以下程式碼複製到 Arduino IDE 中,修改 Wi-Fi 名稱、密碼、巴法雲金鑰和 FMOIP。 然後進行編譯並上傳到 ESP-01S。 在開發板選擇「ESP8266」,並在裝置管理員中找到的正確埠號。
/*
ESP01S + 巴法云 + HTTP API → FMO 設備遠程控制器
功能:
1. 使用巴法云/米家控制 FMO 設備
2. 直接使用本地 HTTP API 控制(備用方案)
需要安裝的 Arduino 函式庫:
1. PubSubClient by Nick O'Leary
2. WebSockets by Markus Sattler
硬體:ESP-01S
GPIO2 為板載 LED(兼狀態指示)
*/
#include <ESP8266WiFi.h>
包含 <WebSocketsClient.h>
包含 <PubSubClient.h>
包含 <ESP8266WebServer.h>
// ================== 用戶配置區 ==================
const char* ssid = "WIFI名稱"; // 僅支援 2.4G WiFi
const char* password = "WIFI密碼";
// 巴法雲配置(https://cloud.bemfa.com)
const char* mqtt_server = "bemfa.com"; // 巴法雲 MQTT 伺服器
const int mqtt_port = 9501; // 非 SSL 端口
const char* mqtt_client_id = "巴法雲憑證"; // 從巴法雲控制台取得的憑證
// 建議改成中文友好的名稱,如 "zhongji001" 或 "diantai001"
const char* mqtt_topic = "FMO001"; // 以 "001" 結尾表示插座,以 "002" 結尾表示燈
// HTTP 伺服器端口
const int HTTP_PORT = 80;
// FMO 設備 WebSocket 配置
const char* fmo_host = "FMO IP 位址";
const int fmo_ws_port = 80;
const char* fmo_ws_path = "/ws";
// =================================================
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
WebSocketsClient wsClient;
ESP8266WebServer httpServer(HTTP_PORT);
// 狀態變數
bool wsConnected = false;
bool mqttConnectedFlag = false;
String pendingCmd = ""; // 暫存要發送的命令
unsigned long lastHeartbeat = 0;
const unsigned long HEARTBEAT_INTERVAL = 30000; // 每 30 秒發送心跳
// LED 引腳(ESP-01S 的 GPIO2,板載藍色 LED,低電壓點亮)
const int LED_PIN = 2;
void setup() {
Serial.begin(115200);
延遲(100);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // 關閉 LED
Serial.println("\n[FMO 控制器] 開始...");
setupWiFi();
// 設定 MQTT
mqttClient.setServer(mqtt_server, mqtt_port);
mqttClient.setCallback(mqttCallback);
mqttClient.setBufferSize(512);
// 設定 WebSocket
wsClient.begin(fmo_host, fmo_ws_port, fmo_ws_path);
wsClient.onEvent(webSocketEvent);
wsClient.setReconnectInterval(5000); // 斷線後5秒重新連線
wsClient.enableHeartbeat(15000, 3000, 2); // WebSocket 心跳
// 設定 HTTP API
setupHTTPServer();
}
void loop() {
// 維護 WiFi
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi 斷線,重新連線...");
setupWiFi();
}
// 維護 MQTT
if (!mqttClient.connected()) {
mqttConnectedFlag = false;
reconnectMQTT();
} else {
if (!mqttConnectedFlag) {
mqttConnectedFlag = true;
Serial.println("MQTT 準備就緒。");
}
}
mqttClient.loop();
// 維護 WebSocket
wsClient.loop();
// 處理 HTTP 請求
httpServer.handleClient();
// 巴法雲心跳(保持設備線上)
unsigned long now = millis();
if (now - lastHeartbeat >= HEARTBEAT_INTERVAL) {
lastHeartbeat = now;
if (mqttClient.connected()) {
mqttClient.publish(mqtt_topic, "ping");
Serial.println("[MQTT] 發送心跳");
}
}
delay(10); // 短暫休眠,防止看門狗復位
}
// ================== 無線網路 ==================
void setupWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("連接到 WiFi");
int 嘗試次數 = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
延遲
}(500);
Serial.print(".");
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // LED 閃爍
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println();
Serial.print("已連接 WiFi, IP 位址: ");
Serial.println(WiFi.localIP());
digitalWrite(LED_PIN, LOW); // LED 恆亮表示已連接 WiFi
} else {
Serial.println("\n無法連接 WiFi,將重新嘗試...");
}
}
// ================== MQTT (巴法云) ==================
void reconnectMQTT() {
Serial.print("正在連接 MQTT...");
if (mqttClient.connect(mqtt_client_id)) {
Serial.println("已連接");
mqttClient.subscribe(mqtt_topic);
Serial.printf("訂閱: %s\n", mqtt_topic);
} else {
Serial.print("失敗,rc=");
Serial.print(mqttClient.state());
Serial.println(" 重新嘗試...");
}
}(5000);
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String msg = "";
for (unsigned int i = 0; i < length; i++) {
msg += (char)payload[i];
}
Serial.println("Received message: " + msg);
} < 長度; i++) {
msg += (char)payload[i];
}
msg.trim();
Serial.print("[MQTT] 收到: ");
Serial.println(msg);
if (msg == "on") {
Serial.println("指令:切換至 STANDARD 模式");
sendFMOCommand(0);
} else if (msg == "off") {
Serial.println("命令:切換到待機模式");
sendFMOCommand(1);
}
// 忽略心跳向下訊息,例如「heartbeat/down」
}
// ================== WebSocket (FMO) ```c++
void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
switch (type) {
case WStype_DISCONNECTED:
wsConnected = false;
Serial.println("[WS] Disconnected from FMO");
digitalWrite(LED_PIN, HIGH); // LED熄滅表示 WS 斷開
break;
case WStype_CONNECTED:
wsConnected = true;
Serial.println("[WS] Connected to FMO");
digitalWrite(LED_PIN, LOW); // LED 亮表示雙連接正常
// 如果有待發送的命令,立即發送
if (pendingCmd.length() > 0) {
sendFMOCommand(pendingCmd.toInt());
pendingCmd = "";
}
break;
case WStype_TEXT:
Serial.printf("[WS] 回應: %s\n", payload);
// 可在此解析 setScreenModeResponse 確認成功
break;
case WStype_ERROR:
Serial.println("[WS] 發生錯誤");
break;
default:
break;
}
}
void sendFMOCommand(int mode) {
// mode: 0 = 標準模式 (開啟), 1 = 待機模式 (關閉)
if (!wsConnected) {
Serial.println("[WS] 未連接,正在儲存指令...");
pendingCmd = String(mode);
return;
}
String json = "{\"type\":\"ui\",\"subType\":\"setScreenMode\",\"data\":{\"mode\":" + String(mode) + "}}";
wsClient.sendTXT(json);
Serial.print("[WS] 已發送: ");
Serial.println(json);
}
// ================== HTTP API ==================
void setupHTTPServer() {
httpServer.on("/", HTTP_GET, handleRoot);
httpServer.on("/on", HTTP_GET, handleOn);
httpServer.on("/off", HTTP_GET, handleOff);
httpServer.on("/status", HTTP_GET, handleStatus);
httpServer.onNotFound(handleNotFound);
httpServer.begin();
Serial.println("[HTTP] 伺服器已在 " + String(HTTP_PORT));
}
void handleRoot() {
String html = "<html><head><元字編碼='UTF-8'><title>FMO 控制器</標題></頭>";
html += "<`style="字體:無襯線;最大寬度:400像素;邊界:40像素自動;文字對齊方式:居中"`>";
html += "<h2>FMO 遠程控制器<<h2>>";
html += "<p>設備 IP:"+ WiFi.localIP().toString()+ "</p>";
html += "<p>WebSocket: " + (wsConnected ? "已連接" : "未連接") + "</p>";
html += "<p>MQTT:"+ (mqttClient.connected() ? "已連接" : "未連接") +</p>";
html += "<br><<a href='/on' style='display:inline-block;padding:20px 40px;margin:10px;font-size:18px;background:#4CAF50;color:white;text-decoration:none;border-radius:8px;'></a>>啟動(標準模式)</a>";
html += "<br><<a href='/off' style='display:inline-block;padding:20px 40px;margin:10px;font-size:18px;background:#f44336;color:white;text-decoration:none;border-radius:8px;'></a>>關閉 (待機模式)</a>";
html += "</身體></html>";
httpServer.send(200, "text/html", html);
}
void handleOn() {
Serial.println("[HTTP] Request: /on");
sendFMOCommand(0);
String json = "{\"status\":\"ok\",\"action\":\"off\",\"mode\":\"standard\"}";
httpServer.send(200, "application/json", json);
}
void handleOff() {
Serial.println("[HTTP] Request: /off");
sendFMOCommand(1);
String json = "{\"status\":\"ok\",\"action\":\"off\",\"mode\":\"standby\"}";
httpServer.send(200, "application/json", json);
}
void handleStatus() {
String json = "{\"status\":\"ok\",\"wifi\":\"connected\",\"ip\":\"" + WiFi.localIP().toString() + "\",\"websocket\":" + String(wsConnected ? "true" : "false") + ",\"mqtt\":" + String(mqttClient.connected() ? "true" : "false") + "}";
httpServer.send(200, "application/json", json);
}
void handleNotFound() {
httpServer.send(404, "application/json", "{\"status\":\"error\",\"message\":\"Not Found\"}");
}