11 #include "SinricProInterface.h"
12 #include "SinricProDeviceInterface.h"
13 #include "SinricProWebsocket.h"
14 #include "SinricProUDP.h"
15 #include "SinricProSignature.h"
16 #include "SinricProMessageid.h"
17 #include "SinricProQueue.h"
25 void begin(String socketAuthToken, String signingKey, String serverURL = SINRICPRO_SERVER_URL);
26 template <
typename DeviceType>
27 DeviceType& add(
const char* deviceId,
unsigned long eventWaitTime = 1000);
29 void add(SinricProDeviceInterface& newDevice);
30 void add(SinricProDeviceInterface* newDevice);
35 typedef std::function<void(
void)> ConnectCallbackHandler;
36 void onConnected(ConnectCallbackHandler cb);
37 void onDisconnected(ConnectCallbackHandler cb);
41 DynamicJsonDocument prepareResponse(JsonDocument& requestMessage);
42 DynamicJsonDocument prepareEvent(
const char* deviceId,
const char* action,
const char* cause)
override;
43 void sendMessage(JsonDocument& jsonMessage)
override;
46 proxy(
SinricProClass* ptr, String deviceId) : ptr(ptr), deviceId(deviceId) {}
49 template <
typename DeviceType>
50 operator DeviceType&() {
return as<DeviceType>(); }
51 template <
typename DeviceType>
52 DeviceType& as() {
return ptr->getDeviceInstance<DeviceType>(deviceId); }
70 proxy
operator[](
const String deviceId) {
return proxy(
this, deviceId); }
73 void setResponseMessage(String &&message) { responseMessageStr = message; }
76 void handleReceiveQueue();
77 void handleSendQueue();
79 void handleRequest(DynamicJsonDocument& requestMessage, interface_t Interface);
80 void handleResponse(DynamicJsonDocument& responseMessage);
82 DynamicJsonDocument prepareRequest(
const char* deviceId,
const char* action);
88 void onConnect() { DEBUG_SINRIC(
"[SinricPro]: Connected to \"%s\"!]\r\n", serverURL.c_str()); }
89 void onDisconnect() { DEBUG_SINRIC(
"[SinricPro]: Disconnect\r\n"); }
91 bool verifyDeviceId(
const char*
id);
92 bool verifyAppKey(
const char* key);
93 bool verifyAppSecret(
const char* secret);
94 void extractTimestamp(JsonDocument &message);
96 SinricProDeviceInterface* getDevice(String deviceId);
98 template <
typename DeviceType>
99 DeviceType& getDeviceInstance(String deviceId);
101 std::vector<SinricProDeviceInterface*> devices;
102 String socketAuthToken;
106 websocketListener _websocketListener;
107 udpListener _udpListener;
108 SinricProQueue_t receiveQueue;
109 SinricProQueue_t sendQueue;
111 unsigned long getTimestamp() {
return baseTimestamp + (millis()/1000); }
112 unsigned long baseTimestamp = 0;
115 String responseMessageStr =
"";
118 SinricProDeviceInterface* SinricProClass::getDevice(String deviceId) {
119 for (
auto& device : devices) {
120 if (deviceId == String(device->getDeviceId()))
return device;
125 template <
typename DeviceType>
126 DeviceType& SinricProClass::getDeviceInstance(String deviceId) {
127 DeviceType* tmp_device = (DeviceType*) getDevice(deviceId);
128 if (tmp_device)
return *tmp_device;
130 DEBUG_SINRIC(
"[SinricPro]: Device \"%s\" does not exist. Creating new device\r\n", deviceId.c_str());
131 DeviceType& tmp_deviceInstance = add<DeviceType>(deviceId.c_str());
134 DEBUG_SINRIC(
"[SinricPro]: Reconnecting to server.\r\n");
138 return tmp_deviceInstance;
159 if (!verifyAppKey(socketAuthToken.c_str())) {
160 DEBUG_SINRIC(
"[SinricPro:begin()]: App-Key \"%s\" is invalid!! Please check your app-key!! SinricPro will not work!\r\n", socketAuthToken.c_str());
163 if (!verifyAppSecret(signingKey.c_str())) {
164 DEBUG_SINRIC(
"[SinricPro:begin()]: App-Secret \"%s\" is invalid!! Please check your app-secret!! SinricPro will not work!\r\n", signingKey.c_str());
174 this->socketAuthToken = socketAuthToken;
175 this->signingKey = signingKey;
176 this->serverURL = serverURL;
178 _udpListener.begin(&receiveQueue);
181 template <
typename DeviceType>
182 DeviceType& SinricProClass::add(
const char* deviceId,
unsigned long eventWaitTime) {
183 DeviceType* newDevice =
new DeviceType(deviceId, eventWaitTime);
184 if (verifyDeviceId(deviceId)){
185 DEBUG_SINRIC(
"[SinricPro:add()]: Adding device with id \"%s\".\r\n", deviceId);
186 newDevice->begin(
this);
187 if (verifyAppKey(socketAuthToken.c_str()) && verifyAppSecret(signingKey.c_str())) _begin =
true;
189 DEBUG_SINRIC(
"[SinricPro:add()]: DeviceId \"%s\" is invalid!! Device will be ignored and will NOT WORK!\r\n", deviceId);
191 devices.push_back(newDevice);
195 __attribute__ ((deprecated(
"Please use DeviceType& myDevice = SinricPro.add<DeviceType>(DeviceId);")))
197 if (!verifyDeviceId(newDevice->getDeviceId()))
return;
198 newDevice->
begin(
this);
199 devices.push_back(newDevice);
202 __attribute__ ((deprecated(
"Please use DeviceType& myDevice = SinricPro.add<DeviceType>(DeviceId);")))
204 if (!verifyDeviceId(newDevice.getDeviceId()))
return;
205 newDevice.
begin(
this);
206 devices.push_back(&newDevice);
225 static bool begin_error =
false;
228 DEBUG_SINRIC(
"[SinricPro:handle()]: ERROR! SinricPro.begin() failed or was not called prior to event handler\r\n");
229 DEBUG_SINRIC(
"[SinricPro:handle()]: -Reasons include an invalid app-key, invalid app-secret or no valid deviceIds)\r\n");
230 DEBUG_SINRIC(
"[SinricPro:handle()]: -SinricPro is disabled! Check earlier log messages for details.\r\n");
237 if (!isConnected()) connect();
238 _websocketListener.handle();
239 _udpListener.handle();
241 handleReceiveQueue();
245 DynamicJsonDocument SinricProClass::prepareRequest(
const char* deviceId,
const char* action) {
246 DynamicJsonDocument requestMessage(1024);
247 JsonObject header = requestMessage.createNestedObject(
"header");
248 header[
"payloadVersion"] = 2;
249 header[
"signatureVersion"] = 1;
251 JsonObject payload = requestMessage.createNestedObject(
"payload");
252 payload[
"action"] = action;
253 payload[
"createdAt"] = 0;
254 payload[
"deviceId"] = deviceId;
255 payload[
"replyToken"] = MessageID().getID();
256 payload[
"type"] =
"request";
257 payload.createNestedObject(
"value");
258 return requestMessage;
261 void SinricProClass::handleResponse(DynamicJsonDocument& responseMessage) {
262 DEBUG_SINRIC(
"[SinricPro.handleResponse()]:\r\n");
264 #ifndef NODEBUG_SINRIC
265 serializeJsonPretty(responseMessage, DEBUG_ESP_PORT);
270 void SinricProClass::handleRequest(DynamicJsonDocument& requestMessage, interface_t Interface) {
271 DEBUG_SINRIC(
"[SinricPro.handleRequest()]: handling request\r\n");
272 #ifndef NODEBUG_SINRIC
273 serializeJsonPretty(requestMessage, DEBUG_ESP_PORT);
276 DynamicJsonDocument responseMessage = prepareResponse(requestMessage);
279 bool success =
false;
280 const char* deviceId = requestMessage[
"payload"][
"deviceId"];
281 const char* action = requestMessage[
"payload"][
"action"];
282 JsonObject request_value = requestMessage[
"payload"][
"value"];
283 JsonObject response_value = responseMessage[
"payload"][
"value"];
285 for (
auto& device : devices) {
286 if (strcmp(deviceId, device->getDeviceId()) == 0 && success ==
false) {
287 success = device->handleRequest(deviceId, action, request_value, response_value);
288 responseMessage[
"payload"][
"success"] = success;
290 if (responseMessageStr.length() > 0){
291 responseMessage[
"payload"][
"message"] = responseMessageStr;
292 responseMessageStr =
"";
294 responseMessage[
"payload"][
"message"] =
"Device returned an error while processing the request!";
300 String responseString;
301 serializeJson(responseMessage, responseString);
302 sendQueue.push(
new SinricProMessage(Interface, responseString.c_str()));
305 void SinricProClass::handleReceiveQueue() {
306 if (receiveQueue.count() == 0)
return;
308 DEBUG_SINRIC(
"[SinricPro.handleReceiveQueue()]: %i message(s) in receiveQueue\r\n", receiveQueue.count());
309 while (receiveQueue.count() > 0) {
310 SinricProMessage* rawMessage = receiveQueue.pop();
311 DynamicJsonDocument jsonMessage(1024);
312 deserializeJson(jsonMessage, rawMessage->getMessage());
314 bool sigMatch =
false;
316 if (strncmp(rawMessage->getMessage(),
"{\"timestamp\":", 13) == 0 && strlen(rawMessage->getMessage()) <= 26) {
319 sigMatch = verifyMessage(signingKey, jsonMessage);
322 String messageType = jsonMessage[
"payload"][
"type"];
325 DEBUG_SINRIC(
"[SinricPro.handleReceiveQueue()]: Signature is valid. Processing message...\r\n");
326 extractTimestamp(jsonMessage);
327 if (messageType ==
"response") handleResponse(jsonMessage);
328 if (messageType ==
"request") handleRequest(jsonMessage, rawMessage->getInterface());
330 DEBUG_SINRIC(
"[SinricPro.handleReceiveQueue()]: Signature is invalid! Sending messsage to [dev/null] ;)\r\n");
336 void SinricProClass::handleSendQueue() {
337 if (!isConnected())
return;
338 if (!baseTimestamp)
return;
339 while (sendQueue.count() > 0) {
340 DEBUG_SINRIC(
"[SinricPro:handleSendQueue()]: %i message(s) in sendQueue\r\n", sendQueue.count());
341 DEBUG_SINRIC(
"[SinricPro:handleSendQueue()]: Sending message...\r\n");
343 SinricProMessage* rawMessage = sendQueue.pop();
345 DynamicJsonDocument jsonMessage(1024);
346 deserializeJson(jsonMessage, rawMessage->getMessage());
347 jsonMessage[
"payload"][
"createdAt"] = getTimestamp();
348 signMessage(signingKey, jsonMessage);
352 serializeJson(jsonMessage, messageStr);
353 #ifndef NODEBUG_SINRIC
354 serializeJsonPretty(jsonMessage, DEBUG_ESP_PORT);
358 switch (rawMessage->getInterface()) {
359 case IF_WEBSOCKET: DEBUG_SINRIC(
"[SinricPro:handleSendQueue]: Sending to websocket\r\n"); _websocketListener.sendMessage(messageStr);
break;
360 case IF_UDP: DEBUG_SINRIC(
"[SinricPro:handleSendQueue]: Sending to UDP\r\n");_udpListener.sendMessage(messageStr);
break;
364 DEBUG_SINRIC(
"[SinricPro:handleSendQueue()]: message sent.\r\n");
368 void SinricProClass::connect() {
371 for (
auto& device : devices) {
372 const char* deviceId = device->getDeviceId();
373 if (verifyDeviceId(deviceId)) {
374 if (i>0) deviceList +=
';';
375 deviceList += String(deviceId);
381 DEBUG_SINRIC(
"[SinricPro]: ERROR! No valid devices available. Please add a valid device first!\r\n");
385 _websocketListener.begin(serverURL, socketAuthToken, deviceList, &receiveQueue);
389 void SinricProClass::stop() {
390 DEBUG_SINRIC(
"[SinricPro:stop()\r\n");
391 _websocketListener.stop();
394 bool SinricProClass::isConnected() {
395 return _websocketListener.isConnected();
399 void SinricProClass::onConnected(ConnectCallbackHandler cb) {
400 _websocketListener.onConnected(cb);
403 void SinricProClass::onDisconnected(ConnectCallbackHandler cb) {
404 _websocketListener.onDisconnected(cb);
408 void SinricProClass::reconnect() {
409 DEBUG_SINRIC(
"SinricPro:reconnect(): disconnecting\r\n");
411 DEBUG_SINRIC(
"SinricPro:reconnect(): connecting\r\n");
415 bool SinricProClass::verifyDeviceId(
const char*
id) {
416 if (strlen(
id) != 24)
return false;
418 return sscanf(
id,
"%4x%4x%4x%4x%4x%4x%c",
419 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp_c) == 6;
422 bool SinricProClass::verifyAppKey(
const char* key) {
423 if (strlen(key) != 36)
return false;
425 return sscanf(key,
"%4x%4x-%4x-%4x-%4x-%4x%4x%4x%c",
426 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp_c) == 8;
429 bool SinricProClass::verifyAppSecret(
const char* secret) {
430 if (strlen(secret) != 73)
return false;
432 return sscanf(secret,
"%4x%4x-%4x-%4x-%4x-%4x%4x%4x-%4x%4x-%4x-%4x-%4x-%4x%4x%4x%c",
433 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp_c) == 16;
437 void SinricProClass::extractTimestamp(JsonDocument &message) {
438 unsigned long tempTimestamp = 0;
440 tempTimestamp = message[
"timestamp"] | 0;
442 baseTimestamp = tempTimestamp - (millis() / 1000);
443 DEBUG_SINRIC(
"[SinricPro:extractTimestamp(): Got Timestamp %lu\r\n", tempTimestamp);
448 tempTimestamp = message[
"payload"][
"createdAt"] | 0;
450 DEBUG_SINRIC(
"[SinricPro:extractTimestamp(): Got Timestamp %lu\r\n", tempTimestamp);
451 baseTimestamp = tempTimestamp - (millis() / 1000);
457 void SinricProClass::sendMessage(JsonDocument& jsonMessage) {
458 DEBUG_SINRIC(
"[SinricPro:sendMessage()]: pushing message into sendQueue\r\n");
459 String messageString;
460 serializeJson(jsonMessage, messageString);
461 sendQueue.push(
new SinricProMessage(IF_WEBSOCKET, messageString.c_str()));
474 _websocketListener.setRestoreDeviceStates(flag);
477 DynamicJsonDocument SinricProClass::prepareResponse(JsonDocument& requestMessage) {
478 DynamicJsonDocument responseMessage(1024);
479 JsonObject header = responseMessage.createNestedObject(
"header");
480 header[
"payloadVersion"] = 2;
481 header[
"signatureVersion"] = 1;
483 JsonObject payload = responseMessage.createNestedObject(
"payload");
484 payload[
"action"] = requestMessage[
"payload"][
"action"];
485 payload[
"clientId"] = requestMessage[
"payload"][
"clientId"];
486 payload[
"createdAt"] = 0;
487 payload[
"deviceId"] = requestMessage[
"payload"][
"deviceId"];
488 payload[
"message"] =
"OK";
489 payload[
"replyToken"] = requestMessage[
"payload"][
"replyToken"];
490 payload[
"success"] =
false;
491 payload[
"type"] =
"response";
492 payload.createNestedObject(
"value");
493 return responseMessage;
497 DynamicJsonDocument SinricProClass::prepareEvent(
const char* deviceId,
const char* action,
const char* cause) {
498 DynamicJsonDocument eventMessage(1024);
499 JsonObject header = eventMessage.createNestedObject(
"header");
500 header[
"payloadVersion"] = 2;
501 header[
"signatureVersion"] = 1;
503 JsonObject payload = eventMessage.createNestedObject(
"payload");
504 payload[
"action"] = action;
505 payload[
"cause"].createNestedObject(
"type");
506 payload[
"cause"][
"type"] = cause;
507 payload[
"createdAt"] = 0;
508 payload[
"deviceId"] = deviceId;
509 payload[
"replyToken"] = MessageID().getID();
510 payload[
"type"] =
"event";
511 payload.createNestedObject(
"value");
515 #ifndef NOSINRIC_INSTANCE