-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhardware.h
More file actions
259 lines (214 loc) · 7.94 KB
/
Copy pathhardware.h
File metadata and controls
259 lines (214 loc) · 7.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#ifndef HARDWARE_H
#define HARDWARE_H
#include <Arduino.h>
#include <string.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include "env.h"
/*
ESP01 hardware abstraction layer.
The common LC Technology / HW-655 / HW-566 relay boards do not always wire
the relay directly to an ESP8266 GPIO. Many of them include a second MCU on
the relay PCB. The ESP01 talks to that MCU over UART using 4-byte binary
commands such as A0 01 01 A2.
Some newer LCTech/Nuvoton variants also expect the ESP01 to look like the
original Espressif AT firmware during boot. If the relay-board MCU sends
AT+CWMODE / AT+RST and receives no answer, it may keep a red LED blinking and
ignore the relay command bytes. This HAL therefore keeps the UART full-duplex
and includes a tiny AT-response bridge while still avoiding Serial.print()
debugging that could be interpreted as relay traffic.
Public HAL surface intentionally kept small:
- setupHardware()
- setLed01(bool)
- setRelay01(bool)
- serviceRelayBoardUart()
- Wi-Fi/mDNS helpers used by the sketch loop
*/
// ---------- Internal hardware constants ----------
// LCTech/Nuvoton boards usually use 115200. Older single-relay boards may use
// 9600; change only this constant if your board matches the older variant.
// static const uint32_t SERIAL_RELAY_BAUD_RATE = 115200;
static const uint32_t SERIAL_RELAY_BAUD_RATE = 9600;
static const SerialConfig SERIAL_RELAY_CONFIG = SERIAL_8N1;
static const uint8_t LED01_PIN = LED_BUILTIN;
static const bool LED01_ACTIVE_LOW = true;
static const bool RELAY_BOOT_DEFAULT_STATE = false;
// Keep relay commands compact and idempotent. Repeating helps some clone boards
// that occasionally miss the first UART frame during boot or Wi-Fi activity.
static const uint8_t RELAY_COMMAND_REPEAT_COUNT = 3;
static const uint16_t RELAY_COMMAND_REPEAT_DELAY_MS = 12;
// Enable the AT bridge required by Nuvoton-style LC Technology relay boards.
static const bool RELAY_BOARD_AT_BRIDGE_ENABLED = true;
static const bool RELAY_BOARD_AT_BOOT_PRIME_ENABLED = true;
static const uint16_t RELAY_BOARD_BOOT_SERVICE_MS = 800;
static const uint8_t RELAY_BOARD_AT_BUFFER_BYTES = 72;
static const uint8_t RELAY_ON_COMMAND[4] = {0xA0, 0x01, 0x01, 0xA2};
static const uint8_t RELAY_OFF_COMMAND[4] = {0xA0, 0x01, 0x00, 0xA1};
static const uint32_t WIFI_CONNECT_TIMEOUT_MS = 15000UL;
static const uint32_t WIFI_RECONNECT_INTERVAL_MS = 10000UL;
static const uint16_t WIFI_CONNECT_RETRY_DELAY_MS = 250;
static const uint16_t HTTP_SERVER_PORT = 80;
// ---------- Internal HAL state ----------
bool relay01 = RELAY_BOOT_DEFAULT_STATE;
bool led01 = false;
bool mdnsResponderStarted = false;
unsigned long lastWiFiReconnectAttemptMs = 0;
char relayBoardAtBuffer[RELAY_BOARD_AT_BUFFER_BYTES];
uint8_t relayBoardAtBufferLength = 0;
// ---------- Relay-board UART / AT bridge helpers ----------
void sendRelayBoardAtReadySequence() {
if (!RELAY_BOARD_AT_BRIDGE_ENABLED) return;
// This sequence mirrors what the relay-board MCU expects from the stock
// ESP8266 AT firmware after AT+RST. It does not control Wi-Fi on our custom
// firmware; it only unlocks the external relay controller's listening mode.
Serial.print(F("WIFI CONNECTED\r\n"));
Serial.print(F("WIFI GOT IP\r\n"));
Serial.print(F("AT+CIPMUX=1\r\n"));
Serial.print(F("AT+CIPSERVER=1,8080\r\n"));
Serial.print(F("AT+CIPSTO=360\r\n"));
Serial.flush();
}
void handleRelayBoardAtCommand(const char *command) {
if (!RELAY_BOARD_AT_BRIDGE_ENABLED) return;
if (strncmp(command, "AT", 2) != 0) return;
// Most boot-time AT commands only require an OK. AT+RST additionally needs
// the fake Wi-Fi-ready banner and TCP-server setup lines used as a bypass key
// by several LC Technology/Nuvoton boards.
Serial.print(F("OK\r\n"));
if (strstr(command, "AT+RST") != nullptr) {
sendRelayBoardAtReadySequence();
}
Serial.flush();
}
void serviceRelayBoardUart() {
if (!RELAY_BOARD_AT_BRIDGE_ENABLED) return;
while (Serial.available() > 0) {
const char current = (char)Serial.read();
if (current == '\r' || current == '\n') {
if (relayBoardAtBufferLength > 0) {
relayBoardAtBuffer[relayBoardAtBufferLength] = '\0';
handleRelayBoardAtCommand(relayBoardAtBuffer);
relayBoardAtBufferLength = 0;
}
continue;
}
if (relayBoardAtBufferLength < RELAY_BOARD_AT_BUFFER_BYTES - 1) {
relayBoardAtBuffer[relayBoardAtBufferLength++] = current;
} else {
// Drop an oversized line rather than risking a partial false command.
relayBoardAtBufferLength = 0;
}
}
}
void delayWithRelayBoardService(uint32_t durationMs) {
const unsigned long startMs = millis();
while (millis() - startMs < durationMs) {
serviceRelayBoardUart();
delay(1);
yield();
}
}
void primeRelayBoardAtBridge() {
if (!RELAY_BOARD_AT_BOOT_PRIME_ENABLED) return;
// Some boards emit AT+RST before the custom sketch reaches loop(). Priming
// the ready sequence is harmless for the UART relay protocol and helps boards
// that otherwise stay in a red-LED waiting loop.
sendRelayBoardAtReadySequence();
delayWithRelayBoardService(RELAY_COMMAND_REPEAT_DELAY_MS);
}
bool setLed01(bool status) {
const uint8_t activeLevel = LED01_ACTIVE_LOW ? LOW : HIGH;
const uint8_t inactiveLevel = LED01_ACTIVE_LOW ? HIGH : LOW;
digitalWrite(LED01_PIN, status ? activeLevel : inactiveLevel);
led01 = status;
return led01;
}
bool setRelay01(bool status) {
const uint8_t *command = status ? RELAY_ON_COMMAND : RELAY_OFF_COMMAND;
serviceRelayBoardUart();
for (uint8_t attempt = 0; attempt < RELAY_COMMAND_REPEAT_COUNT; attempt++) {
Serial.write(command, sizeof(RELAY_ON_COMMAND));
Serial.flush();
delayWithRelayBoardService(RELAY_COMMAND_REPEAT_DELAY_MS);
}
relay01 = status;
return relay01;
}
void setupHardware() {
// Use full-duplex UART. TX sends relay commands; RX listens for boot-time AT
// commands from relay-board MCUs that expect the stock ESP8266 AT firmware.
Serial.begin(SERIAL_RELAY_BAUD_RATE, SERIAL_RELAY_CONFIG);
pinMode(LED01_PIN, OUTPUT);
setLed01(false);
// Do not send a relay OFF command here. The application must first load the
// persisted relay state from EEPROM and then apply that state exactly once.
delayWithRelayBoardService(RELAY_BOARD_BOOT_SERVICE_MS);
primeRelayBoardAtBridge();
}
bool connectWiFiBlocking() {
WiFi.mode(WIFI_STA);
WiFi.hostname(WIFI_HOSTNAME);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
const unsigned long startMs = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startMs < WIFI_CONNECT_TIMEOUT_MS) {
setLed01(!led01);
delayWithRelayBoardService(WIFI_CONNECT_RETRY_DELAY_MS);
}
setLed01(relay01);
return WiFi.status() == WL_CONNECTED;
}
bool isWiFiConnected() {
const wl_status_t currentStatus = WiFi.status();
const bool connected = currentStatus == WL_CONNECTED;
return connected;
}
bool startMdnsResponder() {
if (!isWiFiConnected()) {
mdnsResponderStarted = false;
return false;
}
if (mdnsResponderStarted) {
MDNS.update();
return true;
}
mdnsResponderStarted = MDNS.begin(WIFI_HOSTNAME);
if (mdnsResponderStarted) {
MDNS.addService("http", "tcp", HTTP_SERVER_PORT);
}
return mdnsResponderStarted;
}
void updateMdnsResponder() {
if (!isWiFiConnected()) {
mdnsResponderStarted = false;
return;
}
if (!mdnsResponderStarted) {
startMdnsResponder();
return;
}
MDNS.update();
}
void reconnectWiFiIfNeeded() {
if (isWiFiConnected()) {
lastWiFiReconnectAttemptMs = millis();
return;
}
const unsigned long nowMs = millis();
if (nowMs - lastWiFiReconnectAttemptMs < WIFI_RECONNECT_INTERVAL_MS) {
return;
}
lastWiFiReconnectAttemptMs = nowMs;
mdnsResponderStarted = false;
WiFi.disconnect();
connectWiFiBlocking();
startMdnsResponder();
}
String getDeviceBaseUrl() {
String baseUrl;
baseUrl.reserve(64);
baseUrl += F("http://");
baseUrl += WIFI_HOSTNAME;
baseUrl += F(".local/");
return baseUrl;
}
#endif