From f53a3dec01c25080e00a28a6adbff0a4bc3cd853 Mon Sep 17 00:00:00 2001 From: Andy Blackburn Date: Sat, 21 Jan 2023 22:19:12 +0000 Subject: [PATCH 1/3] fix wifi info for stock and pre 2.11.0 fw (#1003) Co-authored-by: Andrew Blackburn --- tools/flash-shelly.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/flash-shelly.py b/tools/flash-shelly.py index 7f7401ef..4b62e802 100755 --- a/tools/flash-shelly.py +++ b/tools/flash-shelly.py @@ -631,6 +631,9 @@ def get_info(self, force_update=False, error_message=True, scanner='Manual'): if self.info.get('stock_fw_model') is None: # TODO remove when 2.9.x is mainstream. self.info['stock_fw_model'] = self.info.get('stock_model') self.info['sys_mode_str'] = self.get_mode(self.info.get('sys_mode')) + if self.info.get('wifi_conn_ssid') is None: # TODO remove when 2.11.x is mainstream. + self.info['wifi_conn_ssid'] = self.info.get('wifi_ssid') + self.info['wifi_conn_rssi'] = self.info.get('wifi_rssi') return True @staticmethod @@ -703,8 +706,8 @@ def get_info(self, force_update=False, error_message=True, scanner='Manual'): self.info['app'] = self.shelly_model(stock_fw_model)[1] self.info['device_id'] = self.info.get('mqtt', {}).get('id', self.friendly_host) self.info['color_mode'] = self.info.get('mode') - self.info['wifi_ssid'] = self.info.get('status', {}).get('wifi_sta', {}).get('ssid') - self.info['wifi_rssi'] = self.info.get('status', {}).get('wifi_sta', {}).get('rssi') + self.info['wifi_conn_ssid'] = self.info.get('status', {}).get('wifi_sta', {}).get('ssid') + self.info['wifi_conn_rssi'] = self.info.get('status', {}).get('wifi_sta', {}).get('rssi') self.info['uptime'] = self.info.get('status', {}).get('uptime', 0) self.info['battery'] = self.info.get('status', {}).get('bat', {}).get('value') return True From cd1a7e0408d0829def91467058f8d92ee5545e5a Mon Sep 17 00:00:00 2001 From: Markus Kirberg Date: Wed, 25 Jan 2023 21:45:43 +0100 Subject: [PATCH 2/3] adaptive lighting support for CCT lights (#1028) this adds the homekit feature adaptive lightning. as this is a non-officially documented feature by apple homekit specification, it relies on the reverse engineering done by homebridge. --- mos.yml | 2 + src/ShellyDuo/shelly_init.cpp | 8 + src/ShellyRGBW2/shelly_init.cpp | 9 + src/shelly_common.hpp | 5 + src/shelly_hap_adaptive_lighting.cpp | 1045 ++++++++++++++++++++++++++ src/shelly_hap_adaptive_lighting.hpp | 195 +++++ src/shelly_hap_light_bulb.cpp | 19 +- src/shelly_hap_light_bulb.hpp | 18 +- 8 files changed, 1294 insertions(+), 7 deletions(-) create mode 100644 src/shelly_hap_adaptive_lighting.cpp create mode 100644 src/shelly_hap_adaptive_lighting.hpp diff --git a/mos.yml b/mos.yml index 52e7e99a..4dd29fc4 100644 --- a/mos.yml +++ b/mos.yml @@ -145,6 +145,7 @@ libs: - location: https://github.com/mongoose-os-libs/core - location: https://github.com/mongoose-os-libs/file-logger - location: https://github.com/mongoose-os-libs/homekit-adk + version: pull/10/head # use unmerged changes from https://github.com/mongoose-os-libs/homekit-adk/pull/10 - location: https://github.com/mongoose-os-libs/http-server - location: https://github.com/mongoose-os-libs/ota-http-server - location: https://github.com/mongoose-os-libs/rpc-service-config @@ -187,6 +188,7 @@ conds: # XXX: fix size calculation with and without BLE XHAP_ACCESSORY_SERVER_SIZE: 1680 HAP_LOG_LEVEL: 0 # This saves 36K on esp8266. + HAP_TLV_NO_LOG: 1 # Saves us stack HAP_DISABLE_ASSERTS: 1 # 32K HAP_DISABLE_PRECONDITIONS: 1 # 40K diff --git a/src/ShellyDuo/shelly_init.cpp b/src/ShellyDuo/shelly_init.cpp index f51a0331..9cafd8a6 100644 --- a/src/ShellyDuo/shelly_init.cpp +++ b/src/ShellyDuo/shelly_init.cpp @@ -49,6 +49,14 @@ void CreateComponents(std::vector> *comps, return; } + // Use adaptive lightning when possible (CCT) + std::unique_ptr adaptive_light; + adaptive_light.reset(new hap::AdaptiveLighting(hap_light.get(), lb_cfg)); + auto st = adaptive_light->Init(); + if (st.ok()) { + hap_light->SetAdaptiveLight(std::move(adaptive_light)); + } + mgos::hap::Accessory *pri_acc = accs->front().get(); shelly::hap::LightBulb *light_ref = hap_light.get(); hap_light->set_primary(true); diff --git a/src/ShellyRGBW2/shelly_init.cpp b/src/ShellyRGBW2/shelly_init.cpp index d281e12a..a998b68d 100644 --- a/src/ShellyRGBW2/shelly_init.cpp +++ b/src/ShellyRGBW2/shelly_init.cpp @@ -16,6 +16,7 @@ */ #include "shelly_cct_controller.hpp" +#include "shelly_hap_adaptive_lighting.hpp" #include "shelly_hap_input.hpp" #include "shelly_hap_light_bulb.hpp" #include "shelly_input_pin.hpp" @@ -114,6 +115,14 @@ void CreateComponents(std::vector> *comps, return; } + // Use adaptive lightning when possible (CCT) + std::unique_ptr adaptive_light; + adaptive_light.reset(new hap::AdaptiveLighting(hap_light.get(), lb_cfg)); + st = adaptive_light->Init(); + if (st.ok()) { + hap_light->SetAdaptiveLight(std::move(adaptive_light)); + } + bool to_pri_acc = (ndev == 1); // only device will become primary accessory // regardless of sw_hidden status bool sw_hidden = is_optional && lb_cfg->svc_hidden; diff --git a/src/shelly_common.hpp b/src/shelly_common.hpp index 1f36d8cf..6019382b 100644 --- a/src/shelly_common.hpp +++ b/src/shelly_common.hpp @@ -66,6 +66,11 @@ #define SHELLY_HAP_IID_BASE_TEMPERATURE_SENSOR 0xd00 #define SHELLY_HAP_IID_BASE_LEAK_SENSOR 0xe00 #define SHELLY_HAP_IID_BASE_SMOKE_SENSOR 0xf00 +#define SHELLY_HAP_IID_BASE_ADAPTIVE_LIGHTING 0x1000 + +#define kChangeReasonAuto "AUTO" +#define kChangeReasonAutoWithNotification "AUTO_NOTIFICATION" +#define kCHangeReasonHAP "HAP" namespace shelly { diff --git a/src/shelly_hap_adaptive_lighting.cpp b/src/shelly_hap_adaptive_lighting.cpp new file mode 100644 index 00000000..a442c628 --- /dev/null +++ b/src/shelly_hap_adaptive_lighting.cpp @@ -0,0 +1,1045 @@ +/* + * Copyright (c) Shelly-HomeKit Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "shelly_hap_adaptive_lighting.hpp" + +#include "shelly_hap_light_bulb.hpp" + +// not officially documented, reverse engineering by HomeBridge +// https://github.com/homebridge/HAP-NodeJS/ + +enum class SupportedCharacteristicValueTransitionConfigurationsTypes { + SUPPORTED_TRANSITION_CONFIGURATION = 0x01, +}; + +enum class SupportedValueTransitionConfigurationTypes { + CHARACTERISTIC_IID = 0x01, + TRANSITION_TYPE = 0x02, +}; + +enum class TransitionType { + BRIGHTNESS = 0x01, + COLOR_TEMPERATURE = 0x02, +}; + +enum class TransitionControlTypes { + READ_CURRENT_VALUE_TRANSITION_CONFIGURATION = 0x01, + UPDATE_VALUE_TRANSITION_CONFIGURATION = 0x02, +}; + +enum class ReadValueTransitionConfiguration { + CHARACTERISTIC_IID = 0x01, +}; + +enum class UpdateValueTransitionConfigurationsTypes { + VALUE_TRANSITION_CONFIGURATION = 0x01, +}; + +enum class ValueTransitionConfigurationTypes { + CHARACTERISTIC_IID = 0x01, + TRANSITION_PARAMETERS = 0x02, + UNKNOWN_3 = 0x03, // sent with value = 1 (1 byte) + UNKNOWN_4 = 0x04, // not sent yet by anyone + TRANSITION_CURVE_CONFIGURATION = 0x05, + UPDATE_INTERVAL = 0x06, // 16 bit uint + UNKNOWN_7 = 0x07, // not sent yet by anyone + NOTIFY_INTERVAL_THRESHOLD = 0x08, // 32 bit uint +}; + +enum class ValueTransitionParametersTypes { + TRANSITION_ID = 0x01, // 16 bytes + START_TIME = 0x02, // 8 bytes the start time for the provided schedule, + // millis since 2001/01/01 00:00:000 + ID_3 = 0x03, // 8 bytes, id or something (same for multiple writes) +}; + +enum class TransitionCurveConfigurationTypes { + TRANSITION_ENTRY = 0x01, + ADJUSTMENT_CHARACTERISTIC_IID = 0x02, + ADJUSTMENT_MULTIPLIER_RANGE = 0x03, +}; + +enum class TransitionEntryTypes { + ADJUSTMENT_FACTOR = 0x01, + VALUE = 0x02, + OFFSET = 0x03, // the time in milliseconds from the previous + // transition, interpolation happens here + DURATION = 0x04, // optional, default 0, sets how long the previous value + // will stay the same (non interpolation time section) +}; + +enum class TransitionAdjustmentMultiplierRange { + MINIMUM_ADJUSTMENT_MULTIPLIER = 0x01, // brightness 10 + MAXIMUM_ADJUSTMENT_MULTIPLIER = 0x02, // brightness 100 +}; + +enum class ValueTransitionConfigurationResponseTypes { // read format for + // control point + VALUE_CONFIGURATION_STATUS = 0x01, +}; + +enum class + ValueTransitionConfigurationStatusTypes { // note, this could be a + // mirror of + // ValueTransitionConfigurationTypes + // when parameter 0x3 + // would not be bigger suddenly + // than 1 byte received? + CHARACTERISTIC_IID = 0x01, + TRANSITION_PARAMETERS = 0x02, + TIME_SINCE_START = 0x03, // milliseconds since start of transition + }; + +// FIXME: could be moved to homekit-adk +#define kHAPCharacteristicDebugDescription_CharacteristicValueTransitionControl \ + "transition-control" +const HAPUUID kHAPCharacteristicType_CharacteristicValueTransitionControl = + HAPUUIDCreateAppleDefined(0x143); + +#define kHAPCharacteristicDebugDescription_SupportedCharacteristicValueTransitionConfiguration \ + "transition-configuration" +const HAPUUID + kHAPCharacteristicType_SupportedCharacteristicValueTransitionConfiguration = + HAPUUIDCreateAppleDefined(0x144); + +#define kHAPCharacteristicDebugDescription_CharacteristicValueActiveTransitionCount \ + "transition-count" +const HAPUUID kHAPCharacteristicType_CharacteristicValueActiveTransitionCount = + HAPUUIDCreateAppleDefined(0x24B); + +namespace shelly { +namespace hap { + +template +const T clamp(const T &v, const T &lo, const T &hi) { + return std::min(std::max(v, lo), hi); +} + +template +bool isValid(T *unused HAP_UNUSED) { + return true; +} + +HAP_DATA_TLV_SUPPORT(uuidType, uuidFormatType) + +const uuidFormatType uuidFormat = { + .type = kHAPTLVFormatType_Data, + .constraints = {.minLength = 16, .maxLength = 16}}; + +const HAPUInt64TLVFormat uint64Format = { + .type = kHAPTLVFormatType_UInt64, + .constraints = {.minimumValue = 0, .maximumValue = UINT64_MAX}, + .callbacks = {.getDescription = NULL, .getBitDescription = NULL}}; + +const HAPUInt32TLVFormat uint32Format = { + .type = kHAPTLVFormatType_UInt32, + .constraints = {.minimumValue = 0, .maximumValue = UINT32_MAX}, + .callbacks = {.getDescription = NULL, .getBitDescription = NULL}}; + +const HAPUInt8TLVFormat uint8Format = { + .type = kHAPTLVFormatType_UInt8, + .constraints = {.minimumValue = 0, .maximumValue = UINT8_MAX}, + .callbacks = {.getDescription = NULL, .getBitDescription = NULL}}; + +const HAPUInt16TLVFormat uint16Format = { + .type = kHAPTLVFormatType_UInt16, + .constraints = {.minimumValue = 0, .maximumValue = UINT16_MAX}, + .callbacks = {.getDescription = NULL, .getBitDescription = NULL}}; + +const HAPUInt16TLVFormat iidFormat = uint16Format; + +const HAPStructTLVMember transitionEntryAdjustmentFactorMember = { + .valueOffset = HAP_OFFSETOF(transitionEntryType, adjustmentFactor), + .isSetOffset = 0, + .tlvType = (HAPTLVType) TransitionEntryTypes::ADJUSTMENT_FACTOR, + .debugDescription = "ADJUSTMENT_FACTOR", + .format = &uint32Format, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember transitionEntryValueMember = { + .valueOffset = HAP_OFFSETOF(transitionEntryType, value), + .isSetOffset = 0, + .tlvType = (HAPTLVType) TransitionEntryTypes::VALUE, + .debugDescription = "VALUE", + .format = &uint32Format, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember transitionEntryOffsetMember = { + .valueOffset = HAP_OFFSETOF(transitionEntryType, offset), + .isSetOffset = 0, + .tlvType = (HAPTLVType) TransitionEntryTypes::OFFSET, + .debugDescription = "OFFSET", + .format = &uint32Format, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember transitionEntryDurationMember = { + .valueOffset = HAP_OFFSETOF(transitionEntryType, duration), + .isSetOffset = HAP_OFFSETOF(transitionEntryType, durationPresent), + .tlvType = (HAPTLVType) TransitionEntryTypes::DURATION, + .debugDescription = "DURATION", + .format = &uint32Format, + .isOptional = true, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(transitionEntryType, transitionItemFormat) +const transitionItemFormat transitionEntryStructFormat = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){ + &transitionEntryOffsetMember, + &transitionEntryAdjustmentFactorMember, &transitionEntryValueMember, + &transitionEntryDurationMember, NULL}, + .callbacks = {.isValid = isValid}}; + +const HAPUInt8TLVFormat sepFormat = { + .type = kHAPTLVFormatType_None, + .constraints = {.minimumValue = 0, .maximumValue = 0}, + .callbacks = {.getDescription = NULL, .getBitDescription = NULL}}; + +HAP_SEQUENCE_TLV_SUPPORT(transitionCurveType, transitionItemFormat, + curveContainerFormat) + +const curveContainerFormat supportCurveContainer = { + .type = kHAPTLVFormatType_Sequence, + .item = + { + .valueOffset = HAP_OFFSETOF(transitionCurveType, _), + .tlvType = (HAPTLVType) + TransitionCurveConfigurationTypes::TRANSITION_ENTRY, + .debugDescription = "TRANSITION_ENTRY", + .format = &transitionEntryStructFormat, + .isFlat = false, + }, + .separator = { + .tlvType = 0, .debugDescription = "SEPARATOR", .format = &sepFormat}}; + +const HAPStructTLVMember supportCurveContainerMember = { + .valueOffset = HAP_OFFSETOF(transitionCurveConfigurationType, curve), + .isSetOffset = HAP_OFFSETOF(transitionCurveConfigurationType, curvePresent), + .tlvType = (HAPTLVType) 0, // DNC initializer + .debugDescription = "transitionCurveConfigurationMember", + .format = &supportCurveContainer, + .isOptional = false, + .isFlat = true}; + +const HAPStructTLVMember adjustmentCharacteristicIIDMember = { + .valueOffset = HAP_OFFSETOF(transitionCurveConfigurationType, iid), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + TransitionCurveConfigurationTypes::ADJUSTMENT_CHARACTERISTIC_IID, + + .debugDescription = "ADJUSTMENT_CHARACTERISTIC_IID", + .format = &iidFormat, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember minimumAdjustmentMultiplierMember = { + .valueOffset = HAP_OFFSETOF(adjustmentMultiplierRangeType, + minimumAdjustmentMultiplier), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + TransitionAdjustmentMultiplierRange::MINIMUM_ADJUSTMENT_MULTIPLIER, + + .debugDescription = "MINIMUM_ADJUSTMENT_MULTIPLIER", + .format = &uint32Format, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember maximumAdjustmentMultiplierMember = { + .valueOffset = HAP_OFFSETOF(adjustmentMultiplierRangeType, + maximumAdjustmentMultiplier), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + TransitionAdjustmentMultiplierRange::MAXIMUM_ADJUSTMENT_MULTIPLIER, + + .debugDescription = "MAXIMUM_ADJUSTMENT_MULTIPLIER", + .format = &uint32Format, + .isOptional = false, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(adjustmentMultiplierRangeType, + adjustmentMultiplierRangeSType) +const adjustmentMultiplierRangeSType adjustmentMultiplierRangeFormat = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){&maximumAdjustmentMultiplierMember, + &minimumAdjustmentMultiplierMember, + NULL}, + .callbacks = {.isValid = isValid}}; + +const HAPStructTLVMember adjustmentMultiplierRangeMember = { + .valueOffset = HAP_OFFSETOF(transitionCurveConfigurationType, + adjustmentMultiplierRange), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + TransitionCurveConfigurationTypes::ADJUSTMENT_MULTIPLIER_RANGE, + + .debugDescription = "ADJUSTMENT_MULTIPLIER_RANGE", + .format = &adjustmentMultiplierRangeFormat, + .isOptional = false, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(transitionCurveConfigurationType, valueFormatType2) +const valueFormatType2 supportCurveContainerStruct = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){&adjustmentCharacteristicIIDMember, + &adjustmentMultiplierRangeMember, + &supportCurveContainerMember, NULL}, + .callbacks = {.isValid = isValid}}; + +const HAPStructTLVMember transitionCurveConfigurationMember = { + .valueOffset = HAP_OFFSETOF(transitionType, transitionCurveConfiguration), + .isSetOffset = + HAP_OFFSETOF(transitionType, transitionCurveConfigurationPresent), + .tlvType = (HAPTLVType) + ValueTransitionConfigurationTypes::TRANSITION_CURVE_CONFIGURATION, + .debugDescription = "TRANSITION_CURVE_CONFIGURATION", + .format = &supportCurveContainerStruct, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember transitionIdMember = { + .valueOffset = HAP_OFFSETOF(parametersType, transitionId), + .isSetOffset = 0, + .tlvType = (HAPTLVType) ValueTransitionParametersTypes::TRANSITION_ID, + .debugDescription = "TRANSITION_ID", + .format = &uuidFormat, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember startTimeMember = { + .valueOffset = HAP_OFFSETOF(parametersType, startTime), + .isSetOffset = 0, + .tlvType = (HAPTLVType) ValueTransitionParametersTypes::START_TIME, + .debugDescription = "START_TIME", + .format = &uint64Format, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember id3Member = { + .valueOffset = HAP_OFFSETOF(parametersType, id3), + .isSetOffset = 0, + .tlvType = (HAPTLVType) ValueTransitionParametersTypes::ID_3, + .debugDescription = "ID_3", + .format = &uint64Format, + .isOptional = false, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(parametersType, transitionParametersFormatType) +const transitionParametersFormatType transitionParametersFormat = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){&transitionIdMember, + &startTimeMember, &id3Member, NULL}, + .callbacks = {.isValid = isValid}}; + +const HAPStructTLVMember characteristicIIDMember = { + .valueOffset = HAP_OFFSETOF(transitionType, iid), + .isSetOffset = 0, + .tlvType = + (HAPTLVType) ValueTransitionConfigurationTypes::CHARACTERISTIC_IID, + .debugDescription = "CHARACTERISTIC_IID", + .format = &iidFormat, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember transitionParametersMember = { + .valueOffset = HAP_OFFSETOF(transitionType, parameters), + .isSetOffset = HAP_OFFSETOF(transitionType, parametersPresent), + .tlvType = + (HAPTLVType) ValueTransitionConfigurationTypes::TRANSITION_PARAMETERS, + .debugDescription = "TRANSITION_PARAMETERS", + .format = &transitionParametersFormat, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember unknown3Member = { + .valueOffset = HAP_OFFSETOF(transitionType, unknown_3), + .isSetOffset = HAP_OFFSETOF(transitionType, unknown_3Present), + .tlvType = (HAPTLVType) ValueTransitionConfigurationTypes::UNKNOWN_3, + .debugDescription = "UNKNOWN_3", + .format = &uint8Format, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember unknown4Member = { + .valueOffset = HAP_OFFSETOF(transitionType, unknown_4), + .isSetOffset = HAP_OFFSETOF(transitionType, unknown_4Present), + .tlvType = (HAPTLVType) ValueTransitionConfigurationTypes::UNKNOWN_4, + .debugDescription = "UNKNOWN_4", + .format = &uint8Format, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember updateIntervalMember = { + .valueOffset = HAP_OFFSETOF(transitionType, updateInterval), + .isSetOffset = HAP_OFFSETOF(transitionType, updateIntervalPresent), + .tlvType = (HAPTLVType) ValueTransitionConfigurationTypes::UPDATE_INTERVAL, + .debugDescription = "UPDATE_INTERVAL", + .format = &uint16Format, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember unknown7Member = { + .valueOffset = HAP_OFFSETOF(transitionType, unknown_7), + .isSetOffset = HAP_OFFSETOF(transitionType, unknown_7Present), + .tlvType = (HAPTLVType) ValueTransitionConfigurationTypes::UNKNOWN_7, + .debugDescription = "UNKNOWN_7", + .format = &uint16Format, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember notifiyIntervalThresholdMember = { + .valueOffset = HAP_OFFSETOF(transitionType, notifyIntervalThreshold), + .isSetOffset = HAP_OFFSETOF(transitionType, notifyIntervalThresholdPresent), + .tlvType = (HAPTLVType) + ValueTransitionConfigurationTypes::NOTIFY_INTERVAL_THRESHOLD, + .debugDescription = "NOTIFY_INTERVAL_THRESHOLD", + .format = &uint32Format, + .isOptional = true, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(transitionType, valueFormatType) +const valueFormatType valueTypeFormat = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){ + &characteristicIIDMember, &unknown3Member, + &transitionParametersMember, &unknown4Member, + &transitionCurveConfigurationMember, &updateIntervalMember, + &unknown7Member, ¬ifiyIntervalThresholdMember, NULL}, + .callbacks = {.isValid = isValid}}; + +HAP_SEQUENCE_TLV_SUPPORT(valueList, valueFormatType, curveContainerFormat2) + +const curveContainerFormat2 updateValueTransitionFormat = { + .type = kHAPTLVFormatType_Sequence, + .item = + { + .valueOffset = HAP_OFFSETOF(valueList, _), + .tlvType = (HAPTLVType) UpdateValueTransitionConfigurationsTypes:: + VALUE_TRANSITION_CONFIGURATION, + .debugDescription = "VALUE_TRANSITION_CONFIGURATION", + .format = &valueTypeFormat, + .isFlat = false, + }, + .separator = { + .tlvType = 0, .debugDescription = "SEPARATOR", .format = &sepFormat}}; + +const HAPStructTLVMember readValueIIDMember = { + .valueOffset = HAP_OFFSETOF(supportedConfig, iid), + .isSetOffset = 0, + .tlvType = + (HAPTLVType) ReadValueTransitionConfiguration::CHARACTERISTIC_IID, + .debugDescription = "CHARACTERISTIC_IID", + .format = &iidFormat, + .isOptional = false, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(readTransitionType, readValueTransitionFormatType) +const readValueTransitionFormatType readValueTransitionFormat = { + .type = kHAPTLVFormatType_Struct, + .members = (const HAPStructTLVMember *const[]){&readValueIIDMember, NULL}, + .callbacks = {.isValid = isValid}}; + +const HAPStructTLVMember readTransitionMember = { + .valueOffset = HAP_OFFSETOF(transitionControlTypeRequest, readTransition), + .isSetOffset = + HAP_OFFSETOF(transitionControlTypeRequest, readTransitionPresent), + .tlvType = (HAPTLVType) + TransitionControlTypes::READ_CURRENT_VALUE_TRANSITION_CONFIGURATION, + .debugDescription = "READ_CURRENT_VALUE_TRANSITION_CONFIGURATION", + .format = &readValueTransitionFormat, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember updateTransitionMember = { + .valueOffset = HAP_OFFSETOF(transitionControlTypeRequest, updateTransition), + .isSetOffset = + HAP_OFFSETOF(transitionControlTypeRequest, updateTransitionPresent), + .tlvType = (HAPTLVType) + TransitionControlTypes::UPDATE_VALUE_TRANSITION_CONFIGURATION, + .debugDescription = "UPDATE_VALUE_TRANSITION_CONFIGURATION", + .format = &updateValueTransitionFormat, + .isOptional = true, + .isFlat = false}; + +const HAPStructTLVMember readTransitionResponseMember = { + .valueOffset = + HAP_OFFSETOF(transitionControlTypeResponse, readTransitionResponse), + .isSetOffset = HAP_OFFSETOF(transitionControlTypeResponse, + readTransitionResponsePresent), + .tlvType = (HAPTLVType) + TransitionControlTypes::READ_CURRENT_VALUE_TRANSITION_CONFIGURATION, + .debugDescription = "READ_CURRENT_VALUE_TRANSITION_CONFIGURATION_RESP", + .format = &updateValueTransitionFormat, // response contains complete list + .isOptional = true, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(transitionControlTypeRequest, transitionControlFormat) + +const transitionControlFormat transitionControlFormatType = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){&readTransitionMember, + &updateTransitionMember, NULL}, + .callbacks = {.isValid = isValid}}; + +HAP_STRUCT_TLV_SUPPORT(supportedConfig, supportedConfigFormat) + +const HAPStructTLVMember supportedConfigIIDMember = { + .valueOffset = HAP_OFFSETOF(supportedConfig, iid), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + SupportedValueTransitionConfigurationTypes::CHARACTERISTIC_IID, + .debugDescription = "CHARACTERISTIC_IID", + .format = &iidFormat, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember supportedConfigTypeMember = { + .valueOffset = HAP_OFFSETOF(supportedConfig, type), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + SupportedValueTransitionConfigurationTypes::TRANSITION_TYPE, + .debugDescription = "TRANSITION_TYPE", + .format = &uint8Format, + .isOptional = false, + .isFlat = false}; + +const supportedConfigFormat supportedConfigFormatType = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){&supportedConfigIIDMember, + &supportedConfigTypeMember, NULL}, + .callbacks = {.isValid = isValid}}; + +HAP_SEQUENCE_TLV_SUPPORT(supportedConfigList, supportedConfigFormat, + containerFormat) + +const containerFormat supportedTransitionContainer = { + .type = kHAPTLVFormatType_Sequence, + .item = {.valueOffset = HAP_OFFSETOF(supportedConfigList, _), + .tlvType = (HAPTLVType) + SupportedCharacteristicValueTransitionConfigurationsTypes:: + SUPPORTED_TRANSITION_CONFIGURATION, + .debugDescription = "SUPPORTED_TRANSITION_CONFIGURATION", + .format = &supportedConfigFormatType, + .isFlat = false + + }, + .separator = { + .tlvType = 0, .debugDescription = "SEPARATOR", .format = &sepFormat}}; + +const HAPStructTLVMember statusResponseTimeMember = { + .valueOffset = HAP_OFFSETOF(configurationStatus, timeSinceStart), + .isSetOffset = 0, + .tlvType = + (HAPTLVType) ValueTransitionConfigurationStatusTypes::TIME_SINCE_START, + .debugDescription = "TIME_SINCE_START", + .format = &uint32Format, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember statusResponseTransitionMember = { + .valueOffset = HAP_OFFSETOF(configurationStatus, parameters), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + ValueTransitionConfigurationStatusTypes::TRANSITION_PARAMETERS, + .debugDescription = "TRANSITION_PARAMETERS", + .format = &transitionParametersFormat, + .isOptional = false, + .isFlat = false}; + +const HAPStructTLVMember statusResponseIIDMember = { + .valueOffset = HAP_OFFSETOF(configurationStatus, iid), + .isSetOffset = 0, + .tlvType = (HAPTLVType) + ValueTransitionConfigurationStatusTypes::CHARACTERISTIC_IID, + .debugDescription = "CHARACTERISTIC_IID", + .format = &iidFormat, + .isOptional = false, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(configurationStatus, updateTransitionResponseItemFormat) + +const updateTransitionResponseItemFormat valueConfigurationStatusFormat = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){&statusResponseIIDMember, + &statusResponseTransitionMember, + &statusResponseTimeMember, NULL}, + .callbacks = {.isValid = isValid}}; + +HAP_SEQUENCE_TLV_SUPPORT(updateTransitionResponseList, + updateTransitionResponseItemFormat, + updateTransitionResponseFormat) + +const updateTransitionResponseFormat valueConfigurationStatusContainer = { + .type = kHAPTLVFormatType_Sequence, + .item = {.valueOffset = HAP_OFFSETOF(updateTransitionResponseList, _), + .tlvType = (HAPTLVType) ValueTransitionConfigurationResponseTypes:: + VALUE_CONFIGURATION_STATUS, + .debugDescription = "VALUE_CONFIGURATION_STATUS", + .format = &valueConfigurationStatusFormat, + .isFlat = false + + }, + .separator = { + .tlvType = 0, .debugDescription = "SEPARATOR", .format = &sepFormat}}; + +const HAPStructTLVMember updateTransitionReponseMember = { + .valueOffset = + HAP_OFFSETOF(transitionControlTypeResponse, updateTransitionResponse), + .isSetOffset = HAP_OFFSETOF(transitionControlTypeResponse, + updateTransitionResponsePresent), + .tlvType = (HAPTLVType) + TransitionControlTypes::UPDATE_VALUE_TRANSITION_CONFIGURATION, + .debugDescription = "UPDATE_VALUE_TRANSITION_CONFIGURATION_RESP", + .format = &valueConfigurationStatusContainer, + .isOptional = true, + .isFlat = false}; + +HAP_STRUCT_TLV_SUPPORT(transitionControlTypeResponse, + transitionControlResponseFormat) + +const transitionControlResponseFormat transitionControlFormatResponseType = { + .type = kHAPTLVFormatType_Struct, + .members = + (const HAPStructTLVMember *const[]){&readTransitionResponseMember, + &updateTransitionReponseMember, + NULL}, + .callbacks = {.isValid = isValid}}; + +template +HAPError enumerate_vec(HAPSequenceTLVDataSourceRef *dataSource, + HAPSequenceTLVEnumerateCallback callback, + void *_Nullable context) { + T *arr; + HAPRawBufferCopyBytes(&arr, dataSource, sizeof(arr)); + HAPError err = kHAPError_None; + + bool shouldContinue = true; + for (const auto &val : *arr) { + callback(context, (HAPTLVValue *) &val, &shouldContinue); + if (!shouldContinue) { + break; + } + } + return err; +} + +template +void VecToSequence(TVal *val, const TVec *vec) { + val->enumerate = &enumerate_vec; + HAPRawBufferCopyBytes(&val->dataSource, &vec, sizeof(vec)); +} + +AdaptiveLighting::AdaptiveLighting(LightBulb *bulb, struct mgos_config_lb *cfg) + : bulb_(bulb), + cfg_(cfg), + active_transition_count_(0), + update_timer_(std::bind(&AdaptiveLighting::UpdateCB, this)) { + // TODO:restore transition schedule from mgos config / transition_schedule + // caveat: we cannot know how long we were offline for, nor do we know the + // current time, so this seems pointless for now +} + +AdaptiveLighting::~AdaptiveLighting() { +} + +void AdaptiveLighting::Disable() { + update_timer_.Clear(); + active_transition_count_ = 0; + transition_count_characteristic_->RaiseEvent(); +} + +void AdaptiveLighting::ColorTempChangedManually() { + Disable(); +} + +void AdaptiveLighting::BrightnessChangedManually() { + AdjustColorTemp(0); +} + +void AdaptiveLighting::UpdateCB() { + AdjustColorTemp(active_transition_.updateInterval); +} + +void AdaptiveLighting::AdjustColorTemp(uint16_t elapsed_time) { + if (active_transition_count_ != 1) { + return; + } + // eventually we should save the current offset_millis_ so we could + // continue the transition where we left off (except the unknown downtime) + // once we also store/restore the transition table from nvmem + + offset_millis_ += elapsed_time; + notification_millis_ += elapsed_time; + + uint32_t offset_next = 0, offset_curr = 0; + + transitionEntryType curr, next; + curr = active_table_[0]; + next = active_table_[0]; + + // loop over everything until now: could maybe be done more elegantly + // this is only done every 30 minutes + for (auto val = active_table_.cbegin(); val != active_table_.cend(); ++val) { + offset_next += val->offset; + + if (val->durationPresent) { + offset_next += val->duration; + } + + next = *val; + + if (offset_millis_ <= offset_next) { + break; + } + curr = *val; + offset_curr = offset_next; + } + + if (offset_millis_ > offset_next) { + Disable(); + } + + float duration = offset_next - offset_curr; + duration = clamp(duration, 1, INT32_MAX); + + float elapsed = offset_millis_ - offset_curr; + ; + float percentage = elapsed / duration; + + if (curr.durationPresent) { + if (curr.duration > elapsed) { + percentage = 0; + } else { + elapsed -= curr.duration; + duration -= curr.duration; + percentage = elapsed / duration; + } + } + + percentage = clamp(percentage, 0, 1); + + float val_interp = curr.value + (next.value - curr.value) * percentage; + float adj_interp = + curr.adjustmentFactor + + (next.adjustmentFactor - curr.adjustmentFactor) * percentage; + + auto range = &active_transition_.transitionCurveConfiguration + .adjustmentMultiplierRange; + + // could also be another adjustment iid in future + int32_t adjustmentMultiplier = clamp( + (int32_t) cfg_->brightness, range->minimumAdjustmentMultiplier, + range->maximumAdjustmentMultiplier); + + int temperature = val_interp + adj_interp * adjustmentMultiplier; + LOG(LL_INFO, ("adaptive light: %i mired, elapsed in schedule: %f min", + temperature, offset_millis_ / 1000 / 60.0)); + + std::string changereason = kChangeReasonAuto; + if (elapsed_time != 0 && + notification_millis_ >= active_transition_.notifyIntervalThreshold) { + changereason = kChangeReasonAutoWithNotification; + notification_millis_ = 0; + } + + // could also be another value iid in future + // temperature by HAP is sometime beyond bounds. HAP + // Characeristic does not allow this + temperature = clamp(temperature, 50, 400); + bulb_->SetColorTemperature(temperature, changereason); +} + +Status AdaptiveLighting::Init() { + if (bulb_->GetBrightnessCharacteristic() == nullptr) { + LOG(LL_INFO, + ("Adaptive Lighting not supported, no Brightness Characteristic")); + return Status::UNIMPLEMENTED(); + } + if (bulb_->GetColorTemperaturCharacteristic() == nullptr) { + LOG(LL_INFO, ("Adaptive Lighting not supported, no ColorTemperature " + "Characteristic")); + return Status::UNIMPLEMENTED(); + } + + uint16_t iid = SHELLY_HAP_IID_BASE_ADAPTIVE_LIGHTING; + + transition_configuration_characteristic_ = new mgos::hap::TLV8Characteristic( + iid++, + &kHAPCharacteristicType_SupportedCharacteristicValueTransitionConfiguration, + [this](HAPAccessoryServerRef *server UNUSED_ARG, + const HAPTLV8CharacteristicReadRequest *request UNUSED_ARG, + HAPTLVWriterRef *responseWriter, + void *_Nullable context UNUSED_ARG) { + uint16_t iidBrightness = + ((HAPBaseCharacteristic *) bulb_->GetBrightnessCharacteristic() + ->GetHAPCharacteristic()) + ->iid; + uint16_t iidColorTemperature = + ((HAPBaseCharacteristic *) bulb_->GetColorTemperaturCharacteristic() + ->GetHAPCharacteristic()) + ->iid; + + const std::vector vec = { + {.iid = iidColorTemperature, + .type = (uint8_t) TransitionType::COLOR_TEMPERATURE}, + {.iid = iidBrightness, + .type = (uint8_t) TransitionType::BRIGHTNESS}}; + + supportedConfigList val; + VecToSequence(&val, &vec); + + return HAPTLVWriterEncode(responseWriter, &supportedTransitionContainer, + &val); + }, + false /* supports notification */, nullptr, false /* write response */, + false /* control point */, + kHAPCharacteristicDebugDescription_SupportedCharacteristicValueTransitionConfiguration); + bulb_->AddChar(transition_configuration_characteristic_); + + transition_control_characteristic_ = new mgos::hap::TLV8Characteristic( + iid++, &kHAPCharacteristicType_CharacteristicValueTransitionControl, + [this](HAPAccessoryServerRef *server UNUSED_ARG, + const HAPTLV8CharacteristicReadRequest *request UNUSED_ARG, + HAPTLVWriterRef *responseWriter UNUSED_ARG, + void *_Nullable context UNUSED_ARG) { + HAPError err = kHAPError_None; + + std::vector vec; + + if (active_transition_count_ == 1) { + vec.push_back({ + .parameters = + {.startTime = active_transition_.parameters.startTime, + .id3 = active_transition_.parameters.id3, + .transitionId = {.bytes = active_transition_.parameters + .transitionId.bytes, + .numBytes = active_transition_.parameters + .transitionId.numBytes}}, + .timeSinceStart = offset_millis_, + .iid = active_transition_.iid, + }); + } + + updateTransitionResponseList val = {}; + VecToSequence(&val, &vec); + + if (direct_answer_read_ || direct_answer_update_) { + LOG(LL_INFO, ("write_response: read %i, update %i", + direct_answer_read_, direct_answer_update_)); + + transitionControlTypeResponse response = {}; + + response.readTransitionResponsePresent = direct_answer_read_; + response.updateTransitionResponsePresent = direct_answer_update_; + + if (direct_answer_update_) { + direct_answer_update_ = false; + + if (active_transition_count_ == 1) { + response.updateTransitionResponse = val; + } else { + response.updateTransitionResponsePresent = false; + } + } + + updateTransitionType update; + valueList list = {}; + std::vector vec; + + if (direct_answer_read_) { + direct_answer_read_ = false; + vec.push_back(active_transition_); + } + VecToSequence(&list, &vec); + + update.value = list; + response.readTransitionResponse = update; + + err = HAPTLVWriterEncode( + responseWriter, &transitionControlFormatResponseType, &response); + + } else { + LOG(LL_INFO, ("control point: direct read")); + err = HAPTLVWriterEncode(responseWriter, + &valueConfigurationStatusContainer, &val); + } + return err; + }, + false, + [this](HAPAccessoryServerRef *server UNUSED_ARG, + const HAPTLV8CharacteristicWriteRequest *request UNUSED_ARG, + HAPTLVReaderRef *responseReader, + void *_Nullable context UNUSED_ARG) { + HAPPrecondition(responseReader); + + HAPError err = kHAPError_None; + + transitionControlTypeRequest transitionRequest = {}; + + const HAPTLVReader *reader = (const HAPTLVReader *) responseReader; + + LOG(LL_INFO, ("control point: write %zu bytes", reader->numBytes)); + + err = HAPTLVReaderDecode(responseReader, &transitionControlFormatType, + &transitionRequest); + if (err != kHAPError_None) { + LOG(LL_ERROR, ("Error occured while decoding request")); + return err; + } + + if (transitionRequest.readTransitionPresent) { + direct_answer_read_ = true; + + // specific iids will be needed once we would support more than one + // transition + } + + transitionTypeIterationContext transition_context = { + .type = &active_transition_, .count = 0}; + { + valueList *curve = &transitionRequest.updateTransition.value; + + err = curve->enumerate( + &curve->dataSource, + [](void *_Nullable context, HAPTLVValue *value, + bool *shouldContinue) { + transitionTypeIterationContext *enc = + (transitionTypeIterationContext *) context; + + transitionType *v = (transitionType *) value; + + if (enc->count >= 1) { + *shouldContinue = false; + LOG(LL_ERROR, ("transitions are more than supported (1)")); + } else { + *enc->type = *v; + enc->count++; + } + }, + &transition_context); + + if (err != kHAPError_None) { + return err; + } + } + + if (transition_context.count >= 1 && + active_transition_.transitionCurveConfigurationPresent) { + direct_answer_update_ = true; + transitionCurveType *curve = + &active_transition_.transitionCurveConfiguration.curve; + + active_table_.clear(); + tableIterationContext table_context = {.vec = &active_table_, + .count = 0}; + + err = curve->enumerate( + &curve->dataSource, + [](void *_Nullable context, HAPTLVValue *value, + bool HAP_UNUSED *shouldContinue) { + tableIterationContext *enc = (tableIterationContext *) context; + + transitionEntryType *v = (transitionEntryType *) value; + + enc->vec->push_back(*v); + }, + &table_context); + + LOG(LL_INFO, ("Received table with size: %zu", active_table_.size())); + + if (err != kHAPError_None) { + return err; + } + + // encode active_table back for encoding + transitionCurveType *table = + &active_transition_.transitionCurveConfiguration.curve; + + VecToSequence(table, &active_table_); + + // deep copy of parameters.uuid + active_transition_.parameters.transitionId.numBytes = 16; + memcpy(&active_transition_id_, + active_transition_.parameters.transitionId.bytes, + active_transition_.parameters.transitionId.numBytes); + + active_transition_.parameters.transitionId.bytes = + &active_transition_id_; + + uint16_t iidColorTemperature = + ((HAPBaseCharacteristic *) bulb_ + ->GetColorTemperaturCharacteristic() + ->GetHAPCharacteristic()) + ->iid; + + if (active_transition_.iid != iidColorTemperature) { + LOG(LL_ERROR, ("Error occured while decoding request")); + return kHAPError_InvalidState; + } + + if (!active_transition_.unknown_3Present) { + LOG(LL_INFO, ("Schedule deactivated")); + Disable(); + } else { + LOG(LL_INFO, ("Schedule activated")); + // TODO: store configuration as base64 encoded val + // e.g mgos_conf_set_str(&cfg_->transition_schedule, encodedval); + // this would use ~1 kB of storage, but only if we have a notion of + // time + + active_transition_count_ = 1; + transition_count_characteristic_->RaiseEvent(); + offset_millis_ = 0; + notification_millis_ = 0; + + update_timer_.Reset(active_transition_.updateInterval, + MGOS_TIMER_REPEAT | MGOS_TIMER_RUN_NOW); + } + } else { + active_transition_count_ = 0; + } + + return kHAPError_None; + }, + true /* write response */, true /* control point */, + kHAPCharacteristicDebugDescription_CharacteristicValueTransitionControl); + bulb_->AddChar(transition_control_characteristic_); + + transition_count_characteristic_ = new mgos::hap::UInt8Characteristic( + iid++, &kHAPCharacteristicType_CharacteristicValueActiveTransitionCount, + 0, 255, 1, + std::bind(&mgos::hap::ReadUInt8, _1, _2, _3, + &active_transition_count_), + true /* supports_notification */, nullptr, + kHAPCharacteristicDebugDescription_CharacteristicValueActiveTransitionCount); + bulb_->AddChar(transition_count_characteristic_); + + return Status::OK(); +} + +} // namespace hap +} // namespace shelly diff --git a/src/shelly_hap_adaptive_lighting.hpp b/src/shelly_hap_adaptive_lighting.hpp new file mode 100644 index 00000000..6aa21e80 --- /dev/null +++ b/src/shelly_hap_adaptive_lighting.hpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) Shelly-HomeKit Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mgos.hpp" +#include "mgos_hap_chars.hpp" + +#include "HAP+Internal.h" + +namespace shelly { +namespace hap { + +class LightBulb; + +typedef uint16_t iidType; + +typedef struct { + float adjustmentFactor; + float value; + uint32_t offset; + uint32_t duration; + bool durationPresent; +} transitionEntryType; + +typedef struct { + int32_t minimumAdjustmentMultiplier; + int32_t maximumAdjustmentMultiplier; +} adjustmentMultiplierRangeType; + +typedef std::vector curveVectorType; + +typedef struct { + curveVectorType *vec; + size_t count; +} tableIterationContext; + +typedef struct { + HAPError (*enumerate)(HAPSequenceTLVDataSourceRef *dataSource, + HAPSequenceTLVEnumerateCallback callback, + void *context); + HAPSequenceTLVDataSourceRef dataSource; + transitionEntryType _; +} transitionCurveType; + +typedef struct { + transitionCurveType curve; + adjustmentMultiplierRangeType adjustmentMultiplierRange; + iidType iid; + bool curvePresent; +} transitionCurveConfigurationType; + +typedef HAPDataTLVValue uuidType; + +typedef struct { + uint64_t startTime; + uint64_t id3; + uuidType transitionId; +} parametersType; + +typedef struct { // sorted by size for reduced size due to alignment + parametersType parameters; + transitionCurveConfigurationType transitionCurveConfiguration; + uint32_t notifyIntervalThreshold; + uint16_t updateInterval; + uint16_t unknown_7; + iidType iid; + uint8_t unknown_3; + uint8_t unknown_4; + bool unknown_4Present; + bool unknown_3Present; + bool parametersPresent; + bool notifyIntervalThresholdPresent; + bool unknown_7Present; + bool updateIntervalPresent; + bool transitionCurveConfigurationPresent; +} transitionType; + +typedef struct { + iidType iid; +} readTransitionType; + +typedef struct { + HAPError (*enumerate)(HAPSequenceTLVDataSourceRef *dataSource, + HAPSequenceTLVEnumerateCallback callback, + void *context); + HAPSequenceTLVDataSourceRef dataSource; + transitionType _; +} valueList; + +typedef struct { + valueList value; +} updateTransitionType; + +typedef struct { + transitionType *type; + size_t count; +} transitionTypeIterationContext; + +typedef struct { + parametersType parameters; + uint32_t timeSinceStart; + iidType iid; +} configurationStatus; + +typedef struct { + HAPError (*enumerate)(HAPSequenceTLVDataSourceRef *dataSource, + HAPSequenceTLVEnumerateCallback callback, + void *context); + HAPSequenceTLVDataSourceRef dataSource; + configurationStatus _; +} updateTransitionResponseList; + +typedef struct { + updateTransitionType readTransitionResponse; + updateTransitionResponseList updateTransitionResponse; + bool readTransitionResponsePresent; + bool updateTransitionResponsePresent; +} transitionControlTypeResponse; + +typedef struct { + readTransitionType readTransition; + updateTransitionType updateTransition; + bool readTransitionPresent; + bool updateTransitionPresent; +} transitionControlTypeRequest; + +typedef struct { + iidType iid; + uint8_t type; +} supportedConfig; + +typedef struct { + HAPError (*enumerate)(HAPSequenceTLVDataSourceRef *dataSource, + HAPSequenceTLVEnumerateCallback callback, + void *context); + HAPSequenceTLVDataSourceRef dataSource; + supportedConfig _; +} supportedConfigList; + +class AdaptiveLighting { + public: + AdaptiveLighting(LightBulb *bulb, struct mgos_config_lb *cfg); + virtual ~AdaptiveLighting(); + + void ColorTempChangedManually(); + void BrightnessChangedManually(); + + mgos::Status Init(); + + private: + void UpdateCB(); + void Disable(); + void AdjustColorTemp(uint16_t elapsed_time); + + LightBulb *bulb_; + struct mgos_config_lb *cfg_; + + mgos::hap::TLV8Characteristic *transition_configuration_characteristic_ = + nullptr; + mgos::hap::TLV8Characteristic *transition_control_characteristic_ = nullptr; + mgos::hap::UInt8Characteristic *transition_count_characteristic_ = nullptr; + + uint8_t active_transition_count_; // we only support 1 transition + + transitionType active_transition_; + + curveVectorType active_table_; + uint8_t active_transition_id_[16]; + + uint32_t offset_millis_; + uint32_t notification_millis_; + + mgos::Timer update_timer_; + + bool direct_answer_read_ = false; + bool direct_answer_update_ = false; +}; + +} // namespace hap +} // namespace shelly diff --git a/src/shelly_hap_light_bulb.cpp b/src/shelly_hap_light_bulb.cpp index ecf9b126..b5f13a30 100644 --- a/src/shelly_hap_light_bulb.cpp +++ b/src/shelly_hap_light_bulb.cpp @@ -83,7 +83,7 @@ Status LightBulb::Init() { const HAPBoolCharacteristicWriteRequest *request UNUSED_ARG, bool value) { LOG(LL_DEBUG, ("On write %d: %s", id(), OnOff(value))); - UpdateOnOff(value, "HAP"); + UpdateOnOff(value, kCHangeReasonHAP); return kHAPError_None; }, kHAPCharacteristicDebugDescription_On); @@ -98,7 +98,7 @@ Status LightBulb::Init() { uint8_t value) { LOG(LL_DEBUG, ("Brightness write %d: %d", id(), static_cast(value))); - SetBrightness(value, "HAP"); + SetBrightness(value, kCHangeReasonHAP); return kHAPError_None; }, kHAPCharacteristicDebugDescription_Brightness); @@ -119,7 +119,7 @@ Status LightBulb::Init() { uint32_t value) { LOG(LL_INFO, ("Color Temperature write %d: %d", id(), static_cast(value))); - SetColorTemperature(value, "HAP"); + SetColorTemperature(value, kCHangeReasonHAP); return kHAPError_None; }, kHAPCharacteristicDebugDescription_ColorTemperature); @@ -135,7 +135,7 @@ Status LightBulb::Init() { const HAPUInt32CharacteristicWriteRequest *request UNUSED_ARG, uint32_t value) { LOG(LL_DEBUG, ("Hue write %d: %d", id(), static_cast(value))); - SetHue(value, "HAP"); + SetHue(value, kCHangeReasonHAP); return kHAPError_None; }, kHAPCharacteristicDebugDescription_Hue); @@ -148,7 +148,7 @@ Status LightBulb::Init() { [this](HAPAccessoryServerRef *server UNUSED_ARG, const HAPUInt32CharacteristicWriteRequest *request UNUSED_ARG, uint32_t value) { - SetSaturation(value, "HAP"); + SetSaturation(value, kCHangeReasonHAP); return kHAPError_None; }, kHAPCharacteristicDebugDescription_Saturation); @@ -230,9 +230,13 @@ void LightBulb::SetColorTemperature(int color_temperature, cfg_->color_temperature = color_temperature; dirty_ = true; - if (color_temperature_characteristic != nullptr) { + if (color_temperature_characteristic != nullptr && + source != kChangeReasonAuto) { color_temperature_characteristic->RaiseEvent(); } + if (source == kCHangeReasonHAP) { + ad_controller_->ColorTempChangedManually(); + } controller_->UpdateOutput(cfg_, true); } @@ -263,6 +267,9 @@ void LightBulb::SetBrightness(int brightness, const std::string &source) { if (brightness_characteristic != nullptr) { brightness_characteristic->RaiseEvent(); } + if (source == kCHangeReasonHAP) { + ad_controller_->BrightnessChangedManually(); + } controller_->UpdateOutput(cfg_, true); } diff --git a/src/shelly_hap_light_bulb.hpp b/src/shelly_hap_light_bulb.hpp index bfcdf9a8..2f56efe9 100644 --- a/src/shelly_hap_light_bulb.hpp +++ b/src/shelly_hap_light_bulb.hpp @@ -26,6 +26,7 @@ #include "shelly_common.hpp" #include "shelly_component.hpp" +#include "shelly_hap_adaptive_lighting.hpp" #include "shelly_input.hpp" #include "shelly_light_bulb_controller.hpp" #include "shelly_output.hpp" @@ -50,8 +51,22 @@ class LightBulb : public Component, public mgos::hap::Service { Status SetConfig(const std::string &config_json, bool *restart_required) final; Status SetState(const std::string &state_json) final; + + mgos::hap::UInt8Characteristic *GetBrightnessCharacteristic() { + return brightness_characteristic; + } + mgos::hap::UInt32Characteristic *GetColorTemperaturCharacteristic() { + return color_temperature_characteristic; + } + void Identify() final; + void SetAdaptiveLight(std::unique_ptr val) { + ad_controller_ = std::move(val); + } + + void SetColorTemperature(int color_temperature, const std::string &source); + protected: void InputEventHandler(Input::Event ev, bool state); @@ -60,7 +75,7 @@ class LightBulb : public Component, public mgos::hap::Service { void UpdateOnOff(bool on, const std::string &source, bool force = false); void SetHue(int hue, const std::string &source); void SetSaturation(int saturation, const std::string &source); - void SetColorTemperature(int color_temperature, const std::string &source); + void SetBrightness(int brightness, const std::string &source); bool IsAutoOffEnabled() const; @@ -71,6 +86,7 @@ class LightBulb : public Component, public mgos::hap::Service { Input *const in_; std::unique_ptr const controller_; + std::unique_ptr ad_controller_; struct mgos_config_lb *cfg_; bool is_optional_; From 6492dd0cddba146dee24f971241c81c69b72bde6 Mon Sep 17 00:00:00 2001 From: Timo Schilling Date: Wed, 1 Feb 2023 12:20:44 +0100 Subject: [PATCH 3/3] implement carbon dioxide and monoxide sensors (#1210) --- fs_src/index.html | 6 +++ fs_src/script.js | 22 ++++++++-- src/shelly_common.hpp | 4 ++ src/shelly_component.hpp | 2 + src/shelly_hap_carbon_dioxide_sensor.cpp | 49 +++++++++++++++++++++++ src/shelly_hap_carbon_dioxide_sensor.hpp | 36 +++++++++++++++++ src/shelly_hap_carbon_monoxide_sensor.cpp | 49 +++++++++++++++++++++++ src/shelly_hap_carbon_monoxide_sensor.hpp | 36 +++++++++++++++++ src/shelly_hap_input.cpp | 22 ++++++++++ 9 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 src/shelly_hap_carbon_dioxide_sensor.cpp create mode 100644 src/shelly_hap_carbon_dioxide_sensor.hpp create mode 100644 src/shelly_hap_carbon_monoxide_sensor.cpp create mode 100644 src/shelly_hap_carbon_monoxide_sensor.hpp diff --git a/fs_src/index.html b/fs_src/index.html index eece9c02..aaede933 100644 --- a/fs_src/index.html +++ b/fs_src/index.html @@ -550,6 +550,8 @@

Input

+ +
@@ -601,6 +603,8 @@

Disabled Input

+ +
@@ -630,6 +634,8 @@

Sensor

+ +
diff --git a/fs_src/script.js b/fs_src/script.js index ceae40fe..8c7a9623 100644 --- a/fs_src/script.js +++ b/fs_src/script.js @@ -60,7 +60,9 @@ class Component_Type { static kTemperatureSensor = 12; static kLeakSensor = 13; static kSmokeSensor = 14; - static kMax = 15; + static kCarbonMonoxideSensor = 15; + static kCarbonDioxideSensor = 16; + static kMax = 17; }; // Keep in sync with shelly::LightBulbController::BulbType. @@ -592,6 +594,8 @@ function findOrAddContainer(cd) { case Component_Type.kContactSensor: case Component_Type.kLeakSensor: case Component_Type.kSmokeSensor: + case Component_Type.kCarbonMonoxideSensor: + case Component_Type.kCarbonDioxideSensor: c = el("sensor_template").cloneNode(true); c.id = elId; el(c, "save_btn").onclick = function() { @@ -662,6 +666,7 @@ function rgbState(c, newState) { function updateComponent(cd) { let c = findOrAddContainer(cd); + let whatSensor; if (!c) return; switch (cd.type) { case Component_Type.kSwitch: @@ -864,10 +869,19 @@ function updateComponent(cd) { break; } case Component_Type.kMotionSensor: + whatSensor || = "motion"; case Component_Type.kOccupancySensor: + whatSensor || = "occupancy"; case Component_Type.kContactSensor: + whatSensor || = "contact"; case Component_Type.kLeakSensor: - case Component_Type.kSmokeSensor: { + whatSensor || = "leak"; + case Component_Type.kSmokeSensor: + whatSensor || = "smoke"; + case Component_Type.kCarbonMonoxideSensor: + whatSensor || = "carbon monoxide"; + case Component_Type.kCarbonDioxideSensor: { + whatSensor || = "carbon dioxide"; let headText = `Input ${cd.id}`; if (cd.name) headText += ` (${cd.name})`; updateInnerText(el(c, "head"), headText); @@ -878,8 +892,8 @@ function updateComponent(cd) { setValueIfNotModified(el(c, "idle_time"), cd.idle_time); el(c, "idle_time_container").style.display = (cd.in_mode == 0 ? "none" : "block"); - let what = (cd.type == 7 ? "motion" : "occupancy"); - let statusText = (cd.state ? `${what} detected` : `no ${what} detected`); + let statusText = + (cd.state ? `${whatSensor} detected` : `no ${whatSensor} detected`); if (cd.last_ev_age > 0) { statusText += `; last ${secondsToDateString(cd.last_ev_age)} ago`; } diff --git a/src/shelly_common.hpp b/src/shelly_common.hpp index 6019382b..78b69100 100644 --- a/src/shelly_common.hpp +++ b/src/shelly_common.hpp @@ -41,6 +41,8 @@ #define SHELLY_HAP_AID_BASE_TEMPERATURE_SENSOR 0xc00 #define SHELLY_HAP_AID_BASE_LEAK_SENSOR 0xe00 #define SHELLY_HAP_AID_BASE_SMOKE_SENSOR 0xf00 +#define SHELLY_HAP_AID_BASE_CARBON_MONOXIDE_SENSOR 0x1000 +#define SHELLY_HAP_AID_BASE_CARBON_DIOXIDE_SENSOR 0x1100 #define SHELLY_HAP_IID_BASE_SWITCH 0x100 #define SHELLY_HAP_IID_STEP_SWITCH 4 @@ -67,6 +69,8 @@ #define SHELLY_HAP_IID_BASE_LEAK_SENSOR 0xe00 #define SHELLY_HAP_IID_BASE_SMOKE_SENSOR 0xf00 #define SHELLY_HAP_IID_BASE_ADAPTIVE_LIGHTING 0x1000 +#define SHELLY_HAP_IID_BASE_CARBON_MONOXIDE_SENSOR 0x1100 +#define SHELLY_HAP_IID_BASE_CARBON_DIOXIDE_SENSOR 0x1200 #define kChangeReasonAuto "AUTO" #define kChangeReasonAutoWithNotification "AUTO_NOTIFICATION" diff --git a/src/shelly_component.hpp b/src/shelly_component.hpp index f69df0aa..5cea14fa 100644 --- a/src/shelly_component.hpp +++ b/src/shelly_component.hpp @@ -40,6 +40,8 @@ class Component { kTemperatureSensor = 12, kLeakSensor = 13, kSmokeSensor = 14, + kCarbonMonoxideSensor = 15, + kCarbonDioxideSensor = 16, kMax, }; diff --git a/src/shelly_hap_carbon_dioxide_sensor.cpp b/src/shelly_hap_carbon_dioxide_sensor.cpp new file mode 100644 index 00000000..2aab351e --- /dev/null +++ b/src/shelly_hap_carbon_dioxide_sensor.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) Shelly-HomeKit Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "shelly_hap_carbon_dioxide_sensor.hpp" + +namespace shelly { +namespace hap { + +CarbonDioxideSensor::CarbonDioxideSensor(int id, Input *in, + struct mgos_config_in_sensor *cfg) + : SensorBase(id, in, cfg, SHELLY_HAP_IID_BASE_CARBON_DIOXIDE_SENSOR, + &kHAPServiceType_CarbonDioxideSensor, + kHAPServiceDebugDescription_CarbonDioxideSensor) { +} + +CarbonDioxideSensor::~CarbonDioxideSensor() { +} + +Component::Type CarbonDioxideSensor::type() const { + return Type::kCarbonDioxideSensor; +} + +Status CarbonDioxideSensor::Init() { + const Status &st = SensorBase::Init(); + if (!st.ok()) return st; + AddChar(new mgos::hap::UInt8Characteristic( + svc_.iid + 2, &kHAPCharacteristicType_CarbonDioxideDetected, 0, 1, 1, + std::bind(&mgos::hap::ReadUInt8, _1, _2, _3, &state_), + true /* supports_notification */, nullptr /* write_handler */, + kHAPCharacteristicDebugDescription_CarbonDioxideDetected)); + return Status::OK(); +} + +} // namespace hap +} // namespace shelly diff --git a/src/shelly_hap_carbon_dioxide_sensor.hpp b/src/shelly_hap_carbon_dioxide_sensor.hpp new file mode 100644 index 00000000..59526745 --- /dev/null +++ b/src/shelly_hap_carbon_dioxide_sensor.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) Shelly-HomeKit Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "shelly_hap_sensor_base.hpp" + +namespace shelly { +namespace hap { + +class CarbonDioxideSensor : public SensorBase { + public: + CarbonDioxideSensor(int id, Input *in, struct mgos_config_in_sensor *cfg); + virtual ~CarbonDioxideSensor(); + + // Component interface impl. + Status Init() override; + virtual Type type() const override; +}; + +} // namespace hap +} // namespace shelly diff --git a/src/shelly_hap_carbon_monoxide_sensor.cpp b/src/shelly_hap_carbon_monoxide_sensor.cpp new file mode 100644 index 00000000..b0600b56 --- /dev/null +++ b/src/shelly_hap_carbon_monoxide_sensor.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) Shelly-HomeKit Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "shelly_hap_carbon_monoxide_sensor.hpp" + +namespace shelly { +namespace hap { + +CarbonMonoxideSensor::CarbonMonoxideSensor(int id, Input *in, + struct mgos_config_in_sensor *cfg) + : SensorBase(id, in, cfg, SHELLY_HAP_IID_BASE_CARBON_MONOXIDE_SENSOR, + &kHAPServiceType_CarbonMonoxideSensor, + kHAPServiceDebugDescription_CarbonMonoxideSensor) { +} + +CarbonMonoxideSensor::~CarbonMonoxideSensor() { +} + +Component::Type CarbonMonoxideSensor::type() const { + return Type::kCarbonMonoxideSensor; +} + +Status CarbonMonoxideSensor::Init() { + const Status &st = SensorBase::Init(); + if (!st.ok()) return st; + AddChar(new mgos::hap::UInt8Characteristic( + svc_.iid + 2, &kHAPCharacteristicType_CarbonMonoxideDetected, 0, 1, 1, + std::bind(&mgos::hap::ReadUInt8, _1, _2, _3, &state_), + true /* supports_notification */, nullptr /* write_handler */, + kHAPCharacteristicDebugDescription_CarbonMonoxideDetected)); + return Status::OK(); +} + +} // namespace hap +} // namespace shelly diff --git a/src/shelly_hap_carbon_monoxide_sensor.hpp b/src/shelly_hap_carbon_monoxide_sensor.hpp new file mode 100644 index 00000000..3afe46b0 --- /dev/null +++ b/src/shelly_hap_carbon_monoxide_sensor.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) Shelly-HomeKit Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "shelly_hap_sensor_base.hpp" + +namespace shelly { +namespace hap { + +class CarbonMonoxideSensor : public SensorBase { + public: + CarbonMonoxideSensor(int id, Input *in, struct mgos_config_in_sensor *cfg); + virtual ~CarbonMonoxideSensor(); + + // Component interface impl. + Status Init() override; + virtual Type type() const override; +}; + +} // namespace hap +} // namespace shelly diff --git a/src/shelly_hap_input.cpp b/src/shelly_hap_input.cpp index a5fedb78..d7db7d2c 100644 --- a/src/shelly_hap_input.cpp +++ b/src/shelly_hap_input.cpp @@ -20,6 +20,8 @@ #include "mgos.hpp" #include "mgos_hap.h" +#include "shelly_hap_carbon_dioxide_sensor.hpp" +#include "shelly_hap_carbon_monoxide_sensor.hpp" #include "shelly_hap_contact_sensor.hpp" #include "shelly_hap_doorbell.hpp" #include "shelly_hap_leak_sensor.hpp" @@ -156,6 +158,20 @@ Status ShellyInput::Init() { s_ = cs; break; } + case Type::kCarbonMonoxideSensor: { + auto *cs = new hap::CarbonMonoxideSensor( + id(), in_, (struct mgos_config_in_sensor *) &cfg_->sensor); + c_.reset(cs); + s_ = cs; + break; + } + case Type::kCarbonDioxideSensor: { + auto *cs = new hap::CarbonDioxideSensor( + id(), in_, (struct mgos_config_in_sensor *) &cfg_->sensor); + c_.reset(cs); + s_ = cs; + break; + } default: { return mgos::Errorf(STATUS_INVALID_ARGUMENT, "Invalid type %d", (int) initial_type_); @@ -225,6 +241,10 @@ uint16_t ShellyInput::GetAIDBase() const { return SHELLY_HAP_AID_BASE_LEAK_SENSOR; case Type::kSmokeSensor: return SHELLY_HAP_AID_BASE_SMOKE_SENSOR; + case Type::kCarbonMonoxideSensor: + return SHELLY_HAP_AID_BASE_CARBON_MONOXIDE_SENSOR; + case Type::kCarbonDioxideSensor: + return SHELLY_HAP_AID_BASE_CARBON_DIOXIDE_SENSOR; default: return 0; } @@ -245,6 +265,8 @@ bool ShellyInput::IsValidType(int type) { case (int) Type::kDoorbell: case (int) Type::kLeakSensor: case (int) Type::kSmokeSensor: + case (int) Type::kCarbonMonoxideSensor: + case (int) Type::kCarbonDioxideSensor: return true; } return false;