1
+ #include " SciFyIoT.h"
2
+
3
+ // Static instance pointer for callback wrapper
4
+ SciFyIoT* SciFyIoT::instance = nullptr ;
5
+
6
+ // Constructor
7
+ SciFyIoT::SciFyIoT () {
8
+ authenticated = false ;
9
+ lastHeartbeat = 0 ;
10
+ lastActivity = 0 ;
11
+ _commandCallback = nullptr ;
12
+ _statusCallback = nullptr ;
13
+ _serverUrl = " api.scify-tech.com" ;
14
+ _serverPort = 443 ;
15
+ _serverPath = " /ws" ;
16
+
17
+ // Set static instance for callback wrapper
18
+ instance = this ;
19
+ }
20
+
21
+ // Initialize with default server
22
+ void SciFyIoT::begin (const char * ssid, const char * password, const char * apiKey, const char * secret) {
23
+ begin (ssid, password, apiKey, secret, " api.scify-tech.com" , 443 , " /ws" );
24
+ }
25
+
26
+ // Initialize with custom server
27
+ void SciFyIoT::begin (const char * ssid, const char * password, const char * apiKey, const char * secret,
28
+ const char * serverUrl, int serverPort, const char * serverPath) {
29
+ Serial.begin (115200 );
30
+ delay (1000 );
31
+
32
+ Serial.println (" [SciFyIoT] Starting..." );
33
+
34
+ _ssid = ssid;
35
+ _password = password;
36
+ _apiKey = apiKey;
37
+ _secret = secret;
38
+ _serverUrl = serverUrl;
39
+ _serverPort = serverPort;
40
+ _serverPath = serverPath;
41
+
42
+ pinMode (LED_BUILTIN, OUTPUT);
43
+ digitalWrite (LED_BUILTIN, HIGH);
44
+
45
+ // Connect to WiFi
46
+ connectWiFi ();
47
+
48
+ if (WiFi.status () == WL_CONNECTED) {
49
+ webSocket.beginSSL (_serverUrl.c_str (), _serverPort, _serverPath.c_str ());
50
+ webSocket.onEvent (webSocketEventWrapper);
51
+ webSocket.setReconnectInterval (5000 );
52
+ webSocket.enableHeartbeat (15000 , 3000 , 2 );
53
+ lastActivity = millis ();
54
+ Serial.println (" [SciFyIoT] Initialized successfully!" );
55
+ } else {
56
+ Serial.println (" [SciFyIoT] WiFi connection failed" );
57
+ }
58
+ }
59
+
60
+ // Main loop processing
61
+ void SciFyIoT::loop () {
62
+ webSocket.loop ();
63
+
64
+ if (WiFi.status () != WL_CONNECTED) {
65
+ connectWiFi ();
66
+ }
67
+
68
+ if (authenticated && millis () - lastHeartbeat > heartbeatInterval) {
69
+ sendHeartbeat ();
70
+ lastHeartbeat = millis ();
71
+ }
72
+
73
+ checkConnection ();
74
+ delay (50 );
75
+ }
76
+
77
+ // Status methods
78
+ bool SciFyIoT::isConnected () {
79
+ return webSocket.isConnected ();
80
+ }
81
+
82
+ bool SciFyIoT::isAuthenticated () {
83
+ return authenticated;
84
+ }
85
+
86
+ bool SciFyIoT::isWiFiConnected () {
87
+ return WiFi.status () == WL_CONNECTED;
88
+ }
89
+
90
+ // Connect to WiFi
91
+ void SciFyIoT::connectWiFi () {
92
+ WiFi.begin (_ssid.c_str (), _password.c_str ());
93
+
94
+ int attempts = 0 ;
95
+ while (WiFi.status () != WL_CONNECTED && attempts < 30 ) {
96
+ delay (500 );
97
+ attempts++;
98
+ }
99
+
100
+ if (WiFi.status () == WL_CONNECTED) {
101
+ Serial.println (" [WiFi] Connected" );
102
+ } else {
103
+ Serial.println (" [WiFi] Connection failed" );
104
+ }
105
+ }
106
+
107
+ // Static callback wrapper
108
+ void SciFyIoT::webSocketEventWrapper (WStype_t type, uint8_t * payload, size_t length) {
109
+ if (instance) {
110
+ instance->webSocketEvent (type, payload, length);
111
+ }
112
+ }
113
+
114
+ // WebSocket event handler
115
+ void SciFyIoT::webSocketEvent (WStype_t type, uint8_t * payload, size_t length) {
116
+ lastActivity = millis ();
117
+
118
+ switch (type) {
119
+ case WStype_DISCONNECTED:
120
+ authenticated = false ;
121
+ digitalWrite (LED_BUILTIN, HIGH);
122
+ if (_statusCallback) _statusCallback (false , false );
123
+ break ;
124
+
125
+ case WStype_CONNECTED:
126
+ authenticated = false ;
127
+ authenticateDevice ();
128
+ if (_statusCallback) _statusCallback (true , false );
129
+ break ;
130
+
131
+ case WStype_TEXT:
132
+ handleWebSocketMessage ((char *)payload);
133
+ break ;
134
+
135
+ case WStype_ERROR:
136
+ Serial.printf (" [WS] Error: %s\n " , payload);
137
+ break ;
138
+ }
139
+ }
140
+
141
+ // Authenticate with server
142
+ void SciFyIoT::authenticateDevice () {
143
+ DynamicJsonDocument doc (256 );
144
+ doc[" type" ] = " auth" ;
145
+ doc[" apiKey" ] = _apiKey;
146
+ doc[" secret" ] = _secret;
147
+
148
+ String message;
149
+ serializeJson (doc, message);
150
+ webSocket.sendTXT (message);
151
+ }
152
+
153
+ // Handle incoming WebSocket messages
154
+ void SciFyIoT::handleWebSocketMessage (const char * message) {
155
+ DynamicJsonDocument doc (512 );
156
+ DeserializationError error = deserializeJson (doc, message);
157
+
158
+ if (error) return ;
159
+
160
+ String type = doc[" type" ];
161
+
162
+ if (type == " auth_success" ) {
163
+ authenticated = true ;
164
+ digitalWrite (LED_BUILTIN, LOW);
165
+ lastHeartbeat = millis ();
166
+ if (_statusCallback) _statusCallback (true , true );
167
+ }
168
+ else if (type == " auth_error" ) {
169
+ authenticated = false ;
170
+ digitalWrite (LED_BUILTIN, HIGH);
171
+ if (_statusCallback) _statusCallback (true , false );
172
+ }
173
+ else if (type == " command" && authenticated) {
174
+ String commandId = doc[" id" ];
175
+ String payload = doc[" payload" ];
176
+
177
+ bool handled = executeDefaultCommand (payload);
178
+
179
+ if (!handled && _commandCallback) {
180
+ _commandCallback (payload, commandId);
181
+ } else if (handled) {
182
+ sendAck (commandId, " executed" );
183
+ } else {
184
+ sendAck (commandId, " failed" );
185
+ }
186
+ }
187
+ }
188
+
189
+ // Execute default commands
190
+ bool SciFyIoT::executeDefaultCommand (const String& command) {
191
+ if (command == " LED_ON" ) {
192
+ digitalWrite (LED_BUILTIN, LOW);
193
+ return true ;
194
+ }
195
+ else if (command == " LED_OFF" ) {
196
+ digitalWrite (LED_BUILTIN, HIGH);
197
+ return true ;
198
+ }
199
+ else if (command == " STATUS" ) {
200
+ sendSensorData ();
201
+ return true ;
202
+ }
203
+ else if (command == " RESTART" ) {
204
+ ESP.restart ();
205
+ return true ;
206
+ }
207
+
208
+ return false ;
209
+ }
210
+
211
+ // Send acknowledgement
212
+ void SciFyIoT::sendAck (const String& commandId, const String& status) {
213
+ DynamicJsonDocument doc (256 );
214
+ doc[" type" ] = " ack" ;
215
+ doc[" commandId" ] = commandId;
216
+ doc[" status" ] = status;
217
+ doc[" timestamp" ] = millis ();
218
+
219
+ String message;
220
+ serializeJson (doc, message);
221
+ webSocket.sendTXT (message);
222
+ }
223
+
224
+ // Send heartbeat
225
+ void SciFyIoT::sendHeartbeat () {
226
+ if (!authenticated) return ;
227
+
228
+ DynamicJsonDocument doc (128 );
229
+ doc[" type" ] = " heartbeat" ;
230
+ doc[" timestamp" ] = millis ();
231
+
232
+ String message;
233
+ serializeJson (doc, message);
234
+ webSocket.sendTXT (message);
235
+ }
236
+
237
+ // Send sensor data
238
+ void SciFyIoT::sendSensorData (float temperature, float humidity) {
239
+ DynamicJsonDocument doc (256 );
240
+ doc[" type" ] = " sensor_data" ;
241
+ doc[" payload" ][" temperature" ] = temperature;
242
+ doc[" payload" ][" humidity" ] = humidity;
243
+ doc[" payload" ][" uptime" ] = millis ();
244
+ doc[" payload" ][" free_heap" ] = ESP.getFreeHeap ();
245
+ doc[" payload" ][" wifi_rssi" ] = WiFi.RSSI ();
246
+ doc[" timestamp" ] = millis ();
247
+
248
+ String message;
249
+ serializeJson (doc, message);
250
+ webSocket.sendTXT (message);
251
+ }
252
+
253
+ // Send custom data
254
+ void SciFyIoT::sendCustomData (const String& dataType, JsonObject& data) {
255
+ DynamicJsonDocument doc (512 );
256
+ doc[" type" ] = dataType;
257
+ doc[" payload" ] = data;
258
+ doc[" timestamp" ] = millis ();
259
+
260
+ String message;
261
+ serializeJson (doc, message);
262
+ webSocket.sendTXT (message);
263
+ }
264
+
265
+ // Send response
266
+ void SciFyIoT::sendResponse (const String& commandId, const String& status, const String& data) {
267
+ sendAck (commandId, status);
268
+ }
269
+
270
+ // Check connection health
271
+ void SciFyIoT::checkConnection () {
272
+ if (millis () - lastActivity > connectionTimeout) {
273
+ webSocket.disconnect ();
274
+ authenticated = false ;
275
+ lastActivity = millis ();
276
+ }
277
+ }
278
+
279
+ // Set command callback
280
+ void SciFyIoT::onCommandReceived (CommandCallback callback) {
281
+ _commandCallback = callback;
282
+ }
283
+
284
+ // Set status callback
285
+ void SciFyIoT::onStatusChanged (StatusCallback callback) {
286
+ _statusCallback = callback;
287
+ }
288
+
289
+ // Restart device
290
+ void SciFyIoT::restart () {
291
+ ESP.restart ();
292
+ }
293
+
294
+ // Enable/disable built-in LED
295
+ void SciFyIoT::enableBuiltinLED (bool enable) {
296
+ if (enable) {
297
+ pinMode (LED_BUILTIN, OUTPUT);
298
+ }
299
+ }
0 commit comments