diff --git a/changelog.md b/changelog.md index de04657..d5216b8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## Version 3.4.0 + New: + - Support Image upload on camera devices + + Fixed: + - Removed calls to `containsKey` - deprecated since ArduinoJSON 7.2 + - Missing includes + ## Version 3.3.1 - Support SmartButton. diff --git a/library.json b/library.json index 8e5b45c..d03ce87 100644 --- a/library.json +++ b/library.json @@ -13,7 +13,7 @@ "maintainer": true } ], - "version": "3.3.1", + "version": "3.4.0", "frameworks": "arduino", "platforms": [ "espressif8266", diff --git a/library.properties b/library.properties index 5bf0048..223c1ee 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SinricPro -version=3.3.1 +version=3.4.0 author=Boris Jaeger maintainer=Boris Jaeger sentence=Library for https://sinric.pro - simple way to connect your device to alexa diff --git a/src/Capabilities/CameraController.h b/src/Capabilities/CameraController.h new file mode 100644 index 0000000..bda0288 --- /dev/null +++ b/src/Capabilities/CameraController.h @@ -0,0 +1,82 @@ +#pragma once + +#include "../EventLimiter.h" +#include "../SinricProNamespace.h" +#include "../SinricProRequest.h" +#include "../SinricProStrings.h" +#include +#include + +namespace SINRICPRO_NAMESPACE { + +FSTR(CAMERA, getSnapshot); // "getSnapshot" + +using SnapshotCallback = std::function; + +template +class CameraController { + public: + CameraController(); + void onSnapshot(SnapshotCallback cb); + int sendSnapshot(uint8_t* buffer, size_t len); + + protected: + bool handleCameraController(SinricProRequest &request); + + private: + SnapshotCallback getSnapshotCallback = nullptr; +}; + +template +CameraController::CameraController() { + T *device = static_cast(this); + device->registerRequestHandler(std::bind(&CameraController::handleCameraController, this, std::placeholders::_1)); +} + +template +void CameraController::onSnapshot(SnapshotCallback cb) { + getSnapshotCallback = cb; +} + +template +bool CameraController::handleCameraController(SinricProRequest &request) { + T *device = static_cast(this); + + bool success = false; + + if (request.action == FSTR_CAMERA_getSnapshot) { + if (getSnapshotCallback) { + success = getSnapshotCallback(device->deviceId); + } + } + + return success; +} + +template +int CameraController::sendSnapshot(uint8_t* buffer, size_t len) { + T *device = static_cast(this); + + if (!buffer) return -1; + + WiFiClientSecure client; + client.setInsecure(); + + HTTPClient http; + if (!http.begin(client, SINRICPRO_CAMERA_URL, 443, SINRICPRO_CAMERA_PATH, true)) return -1; + + const String& deviceId = device->getDeviceId(); + String timestamp = String(device->getTimestamp()); + String signature = device->sign(deviceId+timestamp); + + http.addHeader(FSTR_SINRICPRO_deviceId, deviceId); + http.addHeader(FSTR_SINRICPRO_timestamp, timestamp); + http.addHeader(FSTR_SINRICPRO_signature, signature); + + int resCode = http.POST(buffer, len); + http.end(); + + return resCode; +} + +} // namespace SINRICPRO_NAMESPACE \ No newline at end of file diff --git a/src/Capabilities/ChannelController.h b/src/Capabilities/ChannelController.h index 96bafbc..847906c 100644 --- a/src/Capabilities/ChannelController.h +++ b/src/Capabilities/ChannelController.h @@ -157,13 +157,13 @@ bool ChannelController::handleChannelController(SinricProRequest &request) { if (request.action == FSTR_CHANNEL_changeChannel) { - if (changeChannelCallback && request.request_value[FSTR_CHANNEL_channel].containsKey(FSTR_CHANNEL_name)) { + if (changeChannelCallback && request.request_value[FSTR_CHANNEL_channel][FSTR_CHANNEL_name].is()) { String channelName = request.request_value[FSTR_CHANNEL_channel][FSTR_CHANNEL_name] | ""; success = changeChannelCallback(device->deviceId, channelName); request.response_value[FSTR_CHANNEL_channel][FSTR_CHANNEL_name] = channelName; } - if (changeChannelNumberCallback && request.request_value[FSTR_CHANNEL_channel].containsKey(FSTR_CHANNEL_number)) { + if (changeChannelNumberCallback && request.request_value[FSTR_CHANNEL_channel][FSTR_CHANNEL_number].is()) { String channelName(""); int channelNumber = request.request_value[FSTR_CHANNEL_channel][FSTR_CHANNEL_number]; success = changeChannelNumberCallback(device->deviceId, channelNumber, channelName); diff --git a/src/Capabilities/ThermostatController.h b/src/Capabilities/ThermostatController.h index c7e8da7..6ac5b44 100644 --- a/src/Capabilities/ThermostatController.h +++ b/src/Capabilities/ThermostatController.h @@ -181,7 +181,7 @@ bool ThermostatController::handleThermostatController(SinricProRequest &reque if (request.action == FSTR_THERMOSTAT_targetTemperature && targetTemperatureCallback) { float temperature; - if (request.request_value.containsKey(FSTR_THERMOSTAT_temperature)) { + if (request.request_value[FSTR_THERMOSTAT_temperature].is()) { temperature = request.request_value[FSTR_THERMOSTAT_temperature]; } else { temperature = 1; diff --git a/src/SinricPro.h b/src/SinricPro.h index 567da20..9cb567a 100644 --- a/src/SinricPro.h +++ b/src/SinricPro.h @@ -10,6 +10,7 @@ #include "SinricProDeviceInterface.h" #include "SinricProInterface.h" #include "SinricProMessageid.h" +#include "SinricProModuleCommandHandler.h" #include "SinricProNamespace.h" #include "SinricProQueue.h" #include "SinricProSignature.h" @@ -17,7 +18,6 @@ #include "SinricProUDP.h" #include "SinricProWebsocket.h" #include "Timestamp.h" -#include "SinricProModuleCommandHandler.h" namespace SINRICPRO_NAMESPACE { /** @@ -57,7 +57,7 @@ using OTAUpdateCallbackHandler = std::function @@ -117,7 +118,7 @@ class SinricProClass : public SinricProInterface { JsonDocument prepareResponse(JsonDocument& requestMessage); JsonDocument prepareEvent(String deviceId, const char* action, const char* cause) override; - void sendMessage(JsonDocument& jsonMessage) override; + void sendMessage(JsonDocument& jsonMessage) override; private: void handleReceiveQueue(); @@ -301,7 +302,7 @@ void SinricProClass::handle() { JsonDocument SinricProClass::prepareRequest(String deviceId, const char* action) { JsonDocument requestMessage; - JsonObject header = requestMessage[FSTR_SINRICPRO_header].to(); + JsonObject header = requestMessage[FSTR_SINRICPRO_header].to(); header[FSTR_SINRICPRO_payloadVersion] = 2; header[FSTR_SINRICPRO_signatureVersion] = 1; @@ -332,20 +333,20 @@ void SinricProClass::handleModuleRequest(JsonDocument& requestMessage, interface #endif JsonDocument responseMessage = prepareResponse(requestMessage); - - String action = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_action] | ""; - JsonObject request_value = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_value]; - JsonObject response_value = responseMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_value]; - SinricProRequest request{ action, "", request_value, response_value}; + + String action = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_action] | ""; + JsonObject request_value = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_value]; + JsonObject response_value = responseMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_value]; + SinricProRequest request{action, "", request_value, response_value}; bool success = _moduleCommandHandler.handleRequest(request); - + responseMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_success] = success; responseMessage[FSTR_SINRICPRO_payload].remove(FSTR_SINRICPRO_deviceId); if (!success) { if (responseMessageStr.length() > 0) { responseMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_message] = responseMessageStr; - responseMessageStr = ""; + responseMessageStr = ""; } else { responseMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_message] = "Module did not handle \"" + action + "\""; } @@ -424,13 +425,13 @@ void SinricProClass::handleReceiveQueue() { DEBUG_SINRIC("[SinricPro.handleReceiveQueue()]: Signature is valid. Processing message...\r\n"); extractTimestamp(jsonMessage); if (messageType == FSTR_SINRICPRO_response) handleResponse(jsonMessage); - if (messageType == FSTR_SINRICPRO_request) { + if (messageType == FSTR_SINRICPRO_request) { String scope = jsonMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_scope] | FSTR_SINRICPRO_device; if (strcmp(FSTR_SINRICPRO_module, scope.c_str()) == 0) { handleModuleRequest(jsonMessage, rawMessage->getInterface()); } else { handleDeviceRequest(jsonMessage, rawMessage->getInterface()); - } + } }; } else { DEBUG_SINRIC("[SinricPro.handleReceiveQueue()]: Signature is invalid! \r\n"); @@ -505,7 +506,7 @@ bool SinricProClass::isConnected() { /** * @brief Set callback function for OTA (Over-The-Air) updates. - * + * * This method registers a callback function that will be called when an OTA update is available. * The callback should handle the process of downloading and applying the update. * @@ -519,7 +520,7 @@ void SinricProClass::onOTAUpdate(OTAUpdateCallbackHandler cb) { /** * @brief Set callback function for setting a module setting. - * + * * This method registers a callback function that will be called when a request to change * a module setting is received. * @return void @@ -538,7 +539,7 @@ void SinricProClass::onSetSetting(SetSettingCallbackHandler cb) { * when the SinricPro system needs to report the device's health status. * * @param cb A function pointer of type ReportHealthCallbackHandler. - * This callback should populate a String with health information and return a boolean + * This callback should populate a String with health information and return a boolean * indicating success or failure of the health reporting process. * @return void * @see ReportHealthCallbackHandler for the definition of the callback function type. @@ -660,17 +661,21 @@ void SinricProClass::setResponseMessage(String&& message) { } /** - * @brief Get the current timestamp + * @brief * - * @return unsigned long current timestamp (unix epoch time) + * return unsigned long current timestamp(unix epoch time) * / */ unsigned long SinricProClass::getTimestamp() { return timestamp.getTimestamp(); } +String SinricProClass::sign(const String& message) { + return HMACbase64(message, appSecret); +} + JsonDocument SinricProClass::prepareResponse(JsonDocument& requestMessage) { JsonDocument responseMessage; - JsonObject header = responseMessage[FSTR_SINRICPRO_header].to(); + JsonObject header = responseMessage[FSTR_SINRICPRO_header].to(); header[FSTR_SINRICPRO_payloadVersion] = 2; header[FSTR_SINRICPRO_signatureVersion] = 1; @@ -680,7 +685,7 @@ JsonDocument SinricProClass::prepareResponse(JsonDocument& requestMessage) { payload[FSTR_SINRICPRO_scope] = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_scope]; payload[FSTR_SINRICPRO_createdAt] = 0; payload[FSTR_SINRICPRO_deviceId] = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_deviceId]; - if (requestMessage[FSTR_SINRICPRO_payload].containsKey(FSTR_SINRICPRO_instanceId)) payload[FSTR_SINRICPRO_instanceId] = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_instanceId]; + if (requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_instanceId].is()) payload[FSTR_SINRICPRO_instanceId] = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_instanceId]; payload[FSTR_SINRICPRO_message] = FSTR_SINRICPRO_OK; payload[FSTR_SINRICPRO_replyToken] = requestMessage[FSTR_SINRICPRO_payload][FSTR_SINRICPRO_replyToken]; payload[FSTR_SINRICPRO_success] = false; @@ -691,7 +696,7 @@ JsonDocument SinricProClass::prepareResponse(JsonDocument& requestMessage) { JsonDocument SinricProClass::prepareEvent(String deviceId, const char* action, const char* cause) { JsonDocument eventMessage; - JsonObject header = eventMessage[FSTR_SINRICPRO_header].to(); + JsonObject header = eventMessage[FSTR_SINRICPRO_header].to(); header[FSTR_SINRICPRO_payloadVersion] = 2; header[FSTR_SINRICPRO_signatureVersion] = 1; diff --git a/src/SinricProCamera.h b/src/SinricProCamera.h index af87fd1..cc85494 100644 --- a/src/SinricProCamera.h +++ b/src/SinricProCamera.h @@ -11,6 +11,7 @@ #include "Capabilities/SettingController.h" #include "Capabilities/PushNotification.h" #include "Capabilities/PowerStateController.h" +#include "Capabilities/CameraController.h" #include "SinricProNamespace.h" namespace SINRICPRO_NAMESPACE { @@ -23,10 +24,12 @@ namespace SINRICPRO_NAMESPACE { class SinricProCamera : public SinricProDevice, public SettingController, public PushNotification, - public PowerStateController { + public PowerStateController, + public CameraController { friend class SettingController; friend class PushNotification; friend class PowerStateController; + friend class CameraController; public: SinricProCamera(const String &deviceId) : SinricProDevice(deviceId, "CAMERA") {} }; diff --git a/src/SinricProConfig.h b/src/SinricProConfig.h index 646db76..c50d005 100644 --- a/src/SinricProConfig.h +++ b/src/SinricProConfig.h @@ -25,13 +25,34 @@ #define WEBSOCKET_SSL #endif +#ifndef SINRICPRO_SERVER_URL #define SINRICPRO_SERVER_URL "ws.sinric.pro" +#endif + +#ifndef SINRICPRO_SERVER_PORT #define SINRICPRO_SERVER_PORT 80 +#endif + +#ifndef SINRICPRO_SERVER_SSL_PORT #define SINRICPRO_SERVER_SSL_PORT 443 +#endif + +#ifndef SINRICPRO_CAMERA_URL +#define SINRICPRO_CAMERA_URL "camera.sinric.pro" +#endif + +#ifndef SINRICPRO_CAMERA_PATH +#define SINRICPRO_CAMERA_PATH "/snapshot" +#endif // UDP Configuration +#ifndef UDP_MUTLICAST_IP #define UDP_MULTICAST_IP IPAddress(224,9,9,9) +#endif + +#ifndef UDP_MULTICAST_PORT #define UDP_MULTICAST_PORT 3333 +#endif // WebSocket Configuration #ifdef DEBUG_WIFI_ISSUE @@ -43,6 +64,14 @@ #define WEBSOCKET_RETRY_COUNT 2 // EventLimiter Configuration +#ifndef EVENT_LIMIT_STATE #define EVENT_LIMIT_STATE 1000 +#endif + +#ifndef EVENT_LIMIT_SENSOR_STATE #define EVENT_LIMIT_SENSOR_STATE EVENT_LIMIT_STATE +#endif + +#ifndef EVENT_LIMIT_SENSOR_VALUE #define EVENT_LIMIT_SENSOR_VALUE 60000 +#endif diff --git a/src/SinricProDevice.h b/src/SinricProDevice.h index 0499562..6678f81 100644 --- a/src/SinricProDevice.h +++ b/src/SinricProDevice.h @@ -33,8 +33,9 @@ class SinricProDevice : public SinricProDeviceInterface { void registerRequestHandler(const SinricProRequestHandler &requestHandler); unsigned long getTimestamp(); + String sign(const String& message); virtual bool sendEvent(JsonDocument &event); - virtual JsonDocument prepareEvent(const char *action, const char *cause); + virtual JsonDocument prepareEvent(const char *action, const char *cause); virtual String getProductType(); virtual void begin(SinricProInterface *eventSender); @@ -74,6 +75,10 @@ JsonDocument SinricProDevice::prepareEvent(const char* action, const char* cause return JsonDocument(); } +String SinricProDevice::sign(const String& message) { + if (eventSender) return eventSender->sign(message); + return ""; +} bool SinricProDevice::sendEvent(JsonDocument& event) { if (!SinricPro.isConnected()) { diff --git a/src/SinricProDeviceInterface.h b/src/SinricProDeviceInterface.h index 25740c2..0eba94f 100644 --- a/src/SinricProDeviceInterface.h +++ b/src/SinricProDeviceInterface.h @@ -1,19 +1,19 @@ #pragma once #include "SinricProInterface.h" -#include "SinricProRequest.h" - #include "SinricProNamespace.h" +#include "SinricProRequest.h" namespace SINRICPRO_NAMESPACE { class SinricProDeviceInterface { - friend class SinricProClass; + friend class SinricProClass; + protected: - virtual bool handleRequest(SinricProRequest &request) = 0; - virtual String getDeviceId() = 0; - virtual String getProductType() = 0; - virtual void begin(SinricProInterface* eventSender) = 0; - virtual unsigned long getTimestamp(); + virtual bool handleRequest(SinricProRequest& request) = 0; + virtual String getDeviceId() = 0; + virtual String getProductType() = 0; + virtual void begin(SinricProInterface* eventSender) = 0; + virtual unsigned long getTimestamp() = 0; }; -} // SINRICPRO_NAMESPACE \ No newline at end of file +} // namespace SINRICPRO_NAMESPACE \ No newline at end of file diff --git a/src/SinricProInterface.h b/src/SinricProInterface.h index 2de9216..38436e5 100644 --- a/src/SinricProInterface.h +++ b/src/SinricProInterface.h @@ -8,18 +8,19 @@ #pragma once #include "ArduinoJson.h" -#include "SinricProQueue.h" - #include "SinricProNamespace.h" +#include "SinricProQueue.h" namespace SINRICPRO_NAMESPACE { class SinricProInterface { - friend class SinricProDevice; + friend class SinricProDevice; + protected: - virtual void sendMessage(JsonDocument& jsonEvent); - virtual JsonDocument prepareEvent(String deviceId, const char* action, const char* cause); - virtual unsigned long getTimestamp(); - virtual bool isConnected(); + virtual void sendMessage(JsonDocument& jsonEvent) = 0; + virtual String sign(const String& message) = 0; + virtual JsonDocument prepareEvent(String deviceId, const char* action, const char* cause) = 0; + virtual unsigned long getTimestamp() = 0; + virtual bool isConnected() = 0; }; -} // SINRICPRO_NAMESPACE \ No newline at end of file +} // namespace SINRICPRO_NAMESPACE \ No newline at end of file diff --git a/src/SinricProModuleCommandHandler.h b/src/SinricProModuleCommandHandler.h index fd0d347..fecfce3 100644 --- a/src/SinricProModuleCommandHandler.h +++ b/src/SinricProModuleCommandHandler.h @@ -8,8 +8,10 @@ #pragma once #include "SinricProRequest.h" - +#include "SinricProStrings.h" #include "SinricProNamespace.h" +#include "SinricProDebug.h" + namespace SINRICPRO_NAMESPACE { FSTR(OTA, otaUpdateAvailable); // "otaUpdateAvailable" diff --git a/src/SinricProSignature.cpp b/src/SinricProSignature.cpp index be8d06d..3d2bc0a 100644 --- a/src/SinricProSignature.cpp +++ b/src/SinricProSignature.cpp @@ -71,7 +71,7 @@ String calculateSignature(const char* key, String payload) { } String signMessage(String key, JsonDocument &jsonMessage) { - if (!jsonMessage.containsKey("signature")) jsonMessage["signature"].to(); + if (!jsonMessage["signature"].is()) jsonMessage["signature"].to(); jsonMessage["signature"]["HMAC"] = calculateSignature(key.c_str(), jsonMessage["payload"]); String signedMessageString; serializeJson(jsonMessage, signedMessageString); diff --git a/src/SinricProVersion.h b/src/SinricProVersion.h index 59024ce..60f49f7 100644 --- a/src/SinricProVersion.h +++ b/src/SinricProVersion.h @@ -5,8 +5,8 @@ // Version Configuration #define SINRICPRO_VERSION_MAJOR 3 -#define SINRICPRO_VERSION_MINOR 3 -#define SINRICPRO_VERSION_REVISION 1 +#define SINRICPRO_VERSION_MINOR 4 +#define SINRICPRO_VERSION_REVISION 0 #define SINRICPRO_VERSION STR(SINRICPRO_VERSION_MAJOR) "." STR(SINRICPRO_VERSION_MINOR) "." STR(SINRICPRO_VERSION_REVISION) #define SINRICPRO_VERSION_STR "SinricPro (v" SINRICPRO_VERSION ")" #define SINRICPRO_VERISON_INT SINRICPRO_VERSION_MAJOR * 1000000 + SINRICPRO_VERSION_MINOR * 1000 + SINRICPRO_VERSION_REVISION \ No newline at end of file