diff --git a/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/proto.h b/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/proto.h index ace5d5dc..16115bd9 100644 --- a/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/proto.h +++ b/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/proto.h @@ -119,7 +119,7 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; // CS - client -> server // SC - server -> client -#define SUPLA_PROTO_VERSION 24 +#define SUPLA_PROTO_VERSION 25 #define SUPLA_PROTO_VERSION_MIN 1 #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO) || defined(SUPLA_DEVICE) @@ -129,7 +129,9 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; // SUPLA_MAX_DATA_SIZE should be bigger then calcfg, device config, channel // config MAXSIZE. Otherwise sending will fail #define SUPLA_MAX_DATA_SIZE 600 // Registration header without channels +#define USE_DEPRECATED_EMEV_V2 // Temporary. It will be removed. #elif defined(ESP8266) +#define USE_DEPRECATED_EMEV_V2 // Temporary. It will be removed. // supla-espressif-esp compilations #define SUPLA_MAX_DATA_SIZE 1536 #else @@ -296,6 +298,7 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CS_CALL_SET_CHANNEL_CONFIG 1220 // ver. >= 21 #define SUPLA_CS_CALL_GET_DEVICE_CONFIG 1240 // ver. >= 21 #define SUPLA_SC_CALL_DEVICE_CONFIG_UPDATE_OR_RESULT 1250 // ver. >= 21 +#define SUPLA_DS_CALL_SET_SUBDEVICE_DETAILS 1260 // ver. >= 25 #define SUPLA_RESULT_RESPONSE_TIMEOUT -8 #define SUPLA_RESULT_CANT_CONNECT_TO_HOST -7 @@ -351,6 +354,9 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_RESULTCODE_NOT_REGISTERED 39 // ver. >= 20 #define SUPLA_RESULTCODE_DENY_CHANNEL_IS_ASSOCIETED_WITH_VBT 40 // >= 20 #define SUPLA_RESULTCODE_DENY_CHANNEL_IS_ASSOCIETED_WITH_PUSH 41 // >= 20 +#define SUPLA_RESULTCODE_RESTART_REQUESTED 42 // ver. >= 25 +#define SUPLA_RESULTCODE_IDENTIFY_REQUESTED 43 // ver. >= 25 +#define SUPLA_RESULTCODE_MALFORMED_EMAIL 44 // ver. >= ? #define SUPLA_OAUTH_RESULTCODE_ERROR 0 // ver. >= 10 #define SUPLA_OAUTH_RESULTCODE_SUCCESS 1 // ver. >= 10 @@ -485,6 +491,8 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNELFNC_CURTAIN 930 // ver. >= 24 #define SUPLA_CHANNELFNC_VERTICAL_BLIND 940 // ver. >= 24 #define SUPLA_CHANNELFNC_ROLLER_GARAGE_DOOR 950 // ver. >= 24 +#define SUPLA_CHANNELFNC_PUMPSWITCH 960 // ver. >= 25 +#define SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH 970 // ver. >= 25 #define SUPLA_BIT_FUNC_CONTROLLINGTHEGATEWAYLOCK 0x00000001 #define SUPLA_BIT_FUNC_CONTROLLINGTHEGATE 0x00000002 @@ -512,6 +520,8 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_BIT_FUNC_CURTAIN 0x00800000 // ver. >= 24 #define SUPLA_BIT_FUNC_VERTICAL_BLIND 0x01000000 // ver. >= 24 #define SUPLA_BIT_FUNC_ROLLER_GARAGE_DOOR 0x02000000 // ver. >= 24 +#define SUPLA_BIT_FUNC_PUMPSWITCH 0x04000000 // ver. >= 25 +#define SUPLA_BIT_FUNC_HEATORCOLDSOURCESWITCH 0x08000000 // ver. >= 25 #define SUPLA_EVENT_CONTROLLINGTHEGATEWAYLOCK 10 #define SUPLA_EVENT_CONTROLLINGTHEGATE 20 @@ -559,13 +569,20 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_MFR_POLIER 15 #define SUPLA_MFR_ERGO_ENERGIA 16 #define SUPLA_MFR_SOMEF 17 +#define SUPLA_MFR_AURATON 18 // BIT map definition for TDS_SuplaRegisterDevice_*::Flags (32 bit) -#define SUPLA_DEVICE_FLAG_CALCFG_ENTER_CFG_MODE 0x0010 // ver. >= 17 -#define SUPLA_DEVICE_FLAG_SLEEP_MODE_ENABLED 0x0020 // ver. >= 18 -#define SUPLA_DEVICE_FLAG_CALCFG_SET_TIME 0x0040 // ver. >= 21 -#define SUPLA_DEVICE_FLAG_DEVICE_CONFIG_SUPPORTED 0x0080 // ver. >= 21 -#define SUPLA_DEVICE_FLAG_DEVICE_LOCKED 0x0100 // ver. >= 22 +#define SUPLA_DEVICE_FLAG_CALCFG_ENTER_CFG_MODE 0x0010 // ver. >= 17 +#define SUPLA_DEVICE_FLAG_SLEEP_MODE_ENABLED 0x0020 // ver. >= 18 +#define SUPLA_DEVICE_FLAG_CALCFG_SET_TIME 0x0040 // ver. >= 21 +#define SUPLA_DEVICE_FLAG_DEVICE_CONFIG_SUPPORTED 0x0080 // ver. >= 21 +#define SUPLA_DEVICE_FLAG_DEVICE_LOCKED 0x0100 // ver. >= 22 +#define SUPLA_DEVICE_FLAG_CALCFG_SUBDEVICE_PAIRING 0x0200 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_CALCFG_IDENTIFY_DEVICE 0x0400 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_CALCFG_RESTART_DEVICE 0x0800 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_ALWAYS_ALLOW_CHANNEL_DELETION 0x1000 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_BLOCK_ADDING_CHANNELS_AFTER_DELETION \ + 0x2000 // ver. >= 25 // BIT map definition for TDS_SuplaRegisterDevice_F::ConfigFields (64 bit) // type: TDeviceConfig_StatusLed @@ -586,6 +603,8 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; // type: TDeviceConfig_HomeScreenOffDelayType #define SUPLA_DEVICE_CONFIG_FIELD_HOME_SCREEN_OFF_DELAY_TYPE \ (1ULL << 7) // v. >= 24 +// type: TDeviceConfig_PowerStatusLed +#define SUPLA_DEVICE_CONFIG_FIELD_POWER_STATUS_LED (1ULL << 8) // v. >= 25 // BIT map definition for TDS_SuplaDeviceChannel_C::Flags (32 bit) #define SUPLA_CHANNEL_FLAG_ZWAVE_BRIDGE 0x0001 // ver. >= 12 @@ -604,10 +623,10 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNEL_FLAG_RS_SBS_AND_STOP_ACTIONS 0x0080 // ver. >= 17 #define SUPLA_CHANNEL_FLAG_RGBW_COMMANDS_SUPPORTED 0x0100 // ver. >= 21 // Free bits for future use: 0x0200, 0x0400, 0x0800 -#define SUPLA_CHANNEL_FLAG_RS_AUTO_CALIBRATION 0x1000 // ver. >= 15 -#define SUPLA_CHANNEL_FLAG_CALCFG_RESET_COUNTERS 0x2000 // ver. >= 15 -#define SUPLA_CHANNEL_FLAG_CALCFG_RECALIBRATE 0x4000 // ver. >= 15 -// Free bits for future use: 0x8000 +#define SUPLA_CHANNEL_FLAG_RS_AUTO_CALIBRATION 0x1000 // ver. >= 15 +#define SUPLA_CHANNEL_FLAG_CALCFG_RESET_COUNTERS 0x2000 // ver. >= 15 +#define SUPLA_CHANNEL_FLAG_CALCFG_RECALIBRATE 0x4000 // ver. >= 15 +#define SUPLA_CHANNEL_FLAG_CALCFG_IDENTIFY_SUBDEVICE 0x8000 // ver. >= 25 #define SUPLA_CHANNEL_FLAG_CHANNELSTATE 0x00010000 // ver. >= 12 #define SUPLA_CHANNEL_FLAG_PHASE1_UNSUPPORTED 0x00020000 // ver. >= 12 #define SUPLA_CHANNEL_FLAG_PHASE2_UNSUPPORTED 0x00040000 // ver. >= 12 @@ -622,9 +641,10 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNEL_FLAG_POSSIBLE_SLEEP_MODE_deprecated \ 0x04000000 // ver. >= 12 DEPRECATED #define SUPLA_CHANNEL_FLAG_RUNTIME_CHANNEL_CONFIG_UPDATE \ - 0x08000000 // ver. >= 21 -#define SUPLA_CHANNEL_FLAG_WEEKLY_SCHEDULE 0x10000000 // ver. >= 21 -#define SUPLA_CHANNEL_FLAG_HAS_PARENT 0x20000000 // ver. >= 21 + 0x08000000 // ver. >= 21 +#define SUPLA_CHANNEL_FLAG_WEEKLY_SCHEDULE 0x10000000 // ver. >= 21 +#define SUPLA_CHANNEL_FLAG_HAS_PARENT 0x20000000 // ver. >= 21 +#define SUPLA_CHANNEL_FLAG_CALCFG_RESTART_SUBDEVICE 0x40000000 // ver. >= 25 #pragma pack(push, 1) typedef struct { @@ -702,7 +722,10 @@ typedef struct { #ifdef USE_DEPRECATED_EMEV_V1 #define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V1 10 #endif /*USE_DEPRECATED_EMEV_V1*/ +#ifdef USE_DEPRECATED_EMEV_V2 #define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V2 12 +#endif /*USE_DEPRECATED_EMEV_V2*/ +#define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V3 14 #define EV_TYPE_IMPULSE_COUNTER_DETAILS_V1 20 #define EV_TYPE_THERMOSTAT_DETAILS_V1 30 #define EV_TYPE_CHANNEL_STATE_V1 40 @@ -822,7 +845,7 @@ typedef struct { #define SUPLA_HVAC_MODE_CMD_SWITCH_TO_MANUAL 10 typedef struct { - unsigned char IsOn; // DS: 0/1 (or 0..100 ?) + unsigned char IsOn; // DS: 0/1 (for on/off) or 2..102 (for 0-100%) unsigned char Mode; // SUPLA_HVAC_MODE_ _supla_int16_t SetpointTemperatureHeat; // * 0.01 Celcius degree - used for heating @@ -905,7 +928,7 @@ typedef struct { unsigned char DefaultIcon; unsigned char SubDeviceId; // 0 - no subdevice, 1..255 - subdevice id -} TDS_SuplaDeviceChannel_E; // ver. >= 25 +} TDS_SuplaDeviceChannel_E; // ver. >= 25 typedef struct { // device -> server @@ -1067,9 +1090,8 @@ typedef struct { unsigned _supla_int16_t channel_report_size; unsigned char channel_report - [CHANNEL_REPORT_MAXSIZE]; // One byte per channel. The meaning of the - // bits is determined by CHANNEL_REPORT_*. - + [CHANNEL_REPORT_MAXSIZE]; // One byte per channel. The meaning of the + // bits is determined by CHANNEL_REPORT_*. } TSD_SuplaRegisterDeviceResult_B; // ver. >= 25 typedef struct { @@ -1427,6 +1449,10 @@ typedef struct { #define CHANNEL_RELATION_TYPE_AUX_THERMOMETER_GENERIC_HEATER 7 #define CHANNEL_RELATION_TYPE_AUX_THERMOMETER_GENERIC_COOLER 8 +#define CHANNEL_RELATION_TYPE_MASTER_THERMOSTAT 20 +#define CHANNEL_RELATION_TYPE_HEAT_OR_COLD_SOURCE_SWITCH 21 +#define CHANNEL_RELATION_TYPE_PUMP_SWITCH 22 + typedef struct { char EOL; // End Of List _supla_int_t Id; @@ -1538,9 +1564,9 @@ typedef struct { } TAction_ShadingSystem_Parameters; // ver. >= 19 typedef struct { - char Brightness; // -1 == Ignore - char ColorBrightness; // -1 == Ignore - unsigned int Color; // 0 == Ignore + char Brightness; // -1 == Ignore + char ColorBrightness; // -1 == Ignore + unsigned _supla_int_t Color; // 0 == Ignore char ColorRandom; char OnOff; char Reserved[8]; @@ -1810,10 +1836,10 @@ typedef struct { #define EM_VAR_FORWARD_ACTIVE_ENERGY_BALANCED 0x2000 #define EM_VAR_REVERSE_ACTIVE_ENERGY_BALANCED 0x4000 -#define EM_VAR_VOLTAGE_PHASE_ANGLE_12 0x10000 // ver. >= 22 -#define EM_VAR_VOLTAGE_PHASE_ANGLE_13 0x20000 // ver. >= 22 -#define EM_VAR_VOLTAGE_PHASE_SEQUENCE 0x40000 // ver. >= 22 -#define EM_VAR_CURRENT_PHASE_SEQUENCE 0x80000 // ver. >= 22 +#define EM_VAR_VOLTAGE_PHASE_ANGLE_12 0x10000 // ver. >= 25 +#define EM_VAR_VOLTAGE_PHASE_ANGLE_13 0x20000 // ver. >= 25 +#define EM_VAR_VOLTAGE_PHASE_SEQUENCE 0x40000 // ver. >= 25 +#define EM_VAR_CURRENT_PHASE_SEQUENCE 0x80000 // ver. >= 25 #define EM_VAR_POWER_ACTIVE_KW 0x100000 #define EM_VAR_POWER_REACTIVE_KVAR 0x200000 @@ -1852,6 +1878,7 @@ typedef struct { } TElectricityMeter_ExtendedValue; // v. >= 10 #endif /*USE_DEPRECATED_EMEV_V1*/ +#ifdef USE_DEPRECATED_EMEV_V2 // [IODevice->Server->Client] typedef struct { unsigned _supla_int64_t total_forward_active_energy[3]; // * 0.00001 kWh @@ -1879,6 +1906,7 @@ typedef struct { TElectricityMeter_Measurement m[EM_MEASUREMENT_COUNT]; // Last variable in // struct! } TElectricityMeter_ExtendedValue_V2; // v. >= 12 +#endif /*USE_DEPRECATED_EMEV_V2*/ // [IODevice->Server->Client] typedef struct { @@ -1915,7 +1943,7 @@ typedef struct { _supla_int_t m_count; TElectricityMeter_Measurement m[EM_MEASUREMENT_COUNT]; // Last variable in // struct! -} TElectricityMeter_ExtendedValue_V3; // v. >= 22 +} TElectricityMeter_ExtendedValue_V3; // v. >= 25 #define EM_VALUE_FLAG_PHASE1_ON 0x01 #define EM_VALUE_FLAG_PHASE2_ON 0x02 @@ -2065,6 +2093,12 @@ typedef struct { #define SUPLA_CALCFG_CMD_RECALIBRATE 8000 // v. >= 15 #define SUPLA_CALCFG_CMD_ENTER_CFG_MODE 9000 // v. >= 17 #define SUPLA_CALCFG_CMD_SET_TIME 9100 // v. >= 21 +#define SUPLA_CALCFG_CMD_START_SUBDEVICE_PAIRING 9200 // v. >= 25 +#define SUPLA_CALCFG_CMD_IDENTIFY_DEVICE 9300 // v. >= 25 +#define SUPLA_CALCFG_CMD_IDENTIFY_SUBDEVICE 9310 // v. >= 25 +#define SUPLA_CALCFG_CMD_RESTART_DEVICE 9400 // v. >= 25 +#define SUPLA_CALCFG_CMD_RESTART_SUBDEVICE 9410 // v. >= 25 +#define SUPLA_CALCFG_CMD_TAKE_OCR_PHOTO 9420 // v. >= 25 #define SUPLA_CALCFG_DATATYPE_RS_SETTINGS 1000 #define SUPLA_CALCFG_DATATYPE_FB_SETTINGS 1100 // v. >= 17 @@ -2116,6 +2150,33 @@ typedef struct { unsigned _supla_int16_t LightSourceLifespan; // 0 - 65535 hours } TCalCfg_LightSourceLifespan; +#define SUPLA_CALCFG_SUBDEVICE_NAME_MAXSIZE 120 + +// Subdevice Pairing result is send in TDS_DeviceCalCfgResult. Possible values: +// SUPLA_CALCFG_RESULT_TRUE - paring result/status is in Data +// SUPLA_CALCFG_RESULT_UNAUTHORIZED - unauthorized +// SUPLA_CALCFG_RESULT_NOT_SUPPORTED - not supported +// Only in case of TRUE, TCalCfg_SubdevicePairingResult is included in +// TDS_DeviceCalCfgResult. +#define SUPLA_CALCFG_PAIRINGRESULT_PROCEDURE_STARTED 0 +#define SUPLA_CALCFG_PAIRINGRESULT_ONGOING 1 +#define SUPLA_CALCFG_PAIRINGRESULT_NO_NEW_DEVICE_FOUND 2 +#define SUPLA_CALCFG_PAIRINGRESULT_SUCCESS 3 +#define SUPLA_CALCFG_PAIRINGRESULT_DEVICE_NOT_SUPPORTED 4 +#define SUPLA_CALCFG_PAIRINGRESULT_RESOURCES_LIMIT_EXCEEDED 5 +#define SUPLA_CALCFG_PAIRINGRESULT_NOT_STARTED_NOT_READY 6 +#define SUPLA_CALCFG_PAIRINGRESULT_NOT_STARTED_BUSY 7 + +typedef struct { + unsigned _supla_int16_t + ElapsedTimeSec; // Time in seconds since procedure was started + unsigned _supla_int16_t MaximumDurationSec; // Time in seconds + unsigned char PairingResult; // SUPLA_CALCFG_PAIRINGRESULT_ + unsigned char NameSize; // including the terminating null byte ('\0') + char Name[SUPLA_CALCFG_SUBDEVICE_NAME_MAXSIZE]; // UTF8. Last variable in + // struct! +} TCalCfg_SubdevicePairingResult; // v. >= 25 + // CALCFG == CALIBRATION / CONFIG typedef struct { _supla_int_t ChannelID; @@ -2284,7 +2345,7 @@ typedef struct { TThermostatValueGroup Group[4]; } TThermostat_ScheduleCfg; // v. >= 11 -// Tempeature definitions for Heatpol thermostat +// Temperature definitions for Heatpol thermostat // TThermostatTemperatureCfg #define TEMPERATURE_INDEX1 0x0001 #define TEMPERATURE_INDEX2 0x0002 @@ -2304,7 +2365,7 @@ typedef struct { unsigned _supla_int16_t Temperature[10]; } TThermostatTemperatureCfg; -// Tempeature definitions for HVAC +// Temperature definitions for HVAC // THVACTemperatureCfg // Below values are settable by user in UI // Temperature below which heating will be enabled as a freeze protection @@ -2445,8 +2506,14 @@ typedef struct { #define SUPLA_CHANNELSTATE_FIELD_BATTERYHEALTH 0x0200 #define SUPLA_CHANNELSTATE_FIELD_BRIDGENODEONLINE 0x0400 #define SUPLA_CHANNELSTATE_FIELD_LASTCONNECTIONRESETCAUSE 0x0800 +// LIGHTSOURCELIFESPAN, LIGHTSOURCEOPERATINGTIME, and OPERATINGTIME are +// mutually exclusive. Use only one of them. #define SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCELIFESPAN 0x1000 #define SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCEOPERATINGTIME 0x2000 +#define SUPLA_CHANNELSTATE_FIELD_OPERATINGTIME 0x4000 +// SWITCHCYCLECOUNT and defualtIconField are is mutually exclusive. Use only one +// of them. +#define SUPLA_CHANNELSTATE_FIELD_SWITCHCYCLECOUNT 0x8000 #define SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN 0 #define SUPLA_LASTCONNECTIONRESETCAUSE_ACTIVITY_TIMEOUT 1 @@ -2460,8 +2527,11 @@ typedef struct { _supla_int_t ChannelID; // Server -> Client unsigned char ChannelNumber; // Device -> Server }; - _supla_int_t Fields; - _supla_int_t defaultIconField; // SUPLA_CHANNELSTATE_FIELD_* + _supla_int_t Fields; // SUPLA_CHANNELSTATE_FIELD_* + union { + _supla_int_t defaultIconField; + unsigned _supla_int_t SwitchCycleCount; + }; unsigned _supla_int_t IPv4; unsigned char MAC[6]; unsigned char BatteryLevel; // 0 - 100% @@ -2479,6 +2549,7 @@ typedef struct { _supla_int16_t LightSourceLifespanLeft; // -327,67 - 100.00% // LightSourceLifespan * 0.01 _supla_int_t LightSourceOperatingTime; // -3932100sec. - 3932100sec. + unsigned _supla_int_t OperatingTime; // time in seconds }; char EmptySpace[2]; // Empty space for future use } TDSC_ChannelState; // v. >= 12 Device -> Server -> Client @@ -2581,11 +2652,11 @@ typedef struct { } TSC_SetRegistrationEnabledResult; // v. >= 12 typedef struct { - int DeviceID; + _supla_int_t DeviceID; } TCS_DeviceReconnectRequest; // v. >= 12 typedef struct { - int DeviceID; + _supla_int_t DeviceID; unsigned char ResultCode; } TSC_DeviceReconnectRequestResult; // v. >= 12 @@ -2618,6 +2689,7 @@ typedef struct { // For SUPLA_CHANNELFNC_HVAC_THERMOSTAT, ALT weekly schedule is used for // cooling subfuction, while standard weelkly schedule is used for heating #define SUPLA_CONFIG_TYPE_ALT_WEEKLY_SCHEDULE 3 +#define SUPLA_CONFIG_TYPE_OCR 4 /******************************************** * DEVICE CONFIG STRUCTURES @@ -2677,6 +2749,10 @@ typedef struct { unsigned char StatusLedType; // SUPLA_DEVCFG_STATUS_LED_ } TDeviceConfig_StatusLed; // v. >= 21 +typedef struct { + unsigned char Disabled; // 1 - true; 0 - false +} TDeviceConfig_PowerStatusLed; // v. >= 25 + typedef struct { unsigned char ScreenBrightness; // 0-100% unsigned char Automatic; // 0 - false; 1 - true @@ -2720,6 +2796,7 @@ typedef struct { #define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_TIME_DATE (1ULL << 4) #define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_TEMPERATURE_TIME (1ULL << 5) #define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_MAIN_AND_AUX_TEMPERATURE (1ULL << 6) +#define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_MODE_OR_TEMPERATURE (1ULL << 7) typedef struct { // bit field with all available modes (reported by device, readonly for other @@ -2933,6 +3010,7 @@ typedef struct { #define SUPLA_HVAC_ALGORITHM_NOT_SET 0 #define SUPLA_HVAC_ALGORITHM_ON_OFF_SETPOINT_MIDDLE (1ULL << 0) #define SUPLA_HVAC_ALGORITHM_ON_OFF_SETPOINT_AT_MOST (1ULL << 1) +#define SUPLA_HVAC_ALGORITHM_PID (1ULL << 2) // HVAC channel validation rules for thermometers: // - MainThermometerChannelNo must be set @@ -3015,6 +3093,63 @@ typedef struct { #define SUPLA_HVAC_SUBFUNCTION_HEAT 1 #define SUPLA_HVAC_SUBFUNCTION_COOL 2 +typedef struct { + unsigned _supla_int_t MainThermometerChannelNoReadonly : 1; + unsigned _supla_int_t MainThermometerChannelNoHidden : 1; + unsigned _supla_int_t AuxThermometerChannelNoReadonly : 1; + unsigned _supla_int_t AuxThermometerChannelNoHidden : 1; + unsigned _supla_int_t BinarySensorChannelNoReadonly : 1; + unsigned _supla_int_t BinarySensorChannelNoHidden : 1; + unsigned _supla_int_t AuxThermometerTypeReadonly : 1; + unsigned _supla_int_t AuxThermometerTypeHidden : 1; + unsigned _supla_int_t AntiFreezeAndOverheatProtectionEnabledReadonly : 1; + unsigned _supla_int_t AntiFreezeAndOverheatProtectionEnabledHidden : 1; + unsigned _supla_int_t UsedAlgorithmReadonly : 1; + unsigned _supla_int_t UsedAlgorithmHidden : 1; + unsigned _supla_int_t MinOnTimeSReadonly : 1; + unsigned _supla_int_t MinOnTimeSHidden : 1; + unsigned _supla_int_t MinOffTimeSReadonly : 1; + unsigned _supla_int_t MinOffTimeSHidden : 1; + unsigned _supla_int_t OutputValueOnErrorReadonly : 1; + unsigned _supla_int_t OutputValueOnErrorHidden : 1; + unsigned _supla_int_t SubfunctionReadonly : 1; + unsigned _supla_int_t SubfunctionHidden : 1; + unsigned _supla_int_t + TemperatureSetpointChangeSwitchesToManualModeReadonly : 1; + unsigned _supla_int_t TemperatureSetpointChangeSwitchesToManualModeHidden : 1; + unsigned _supla_int_t AuxMinMaxSetpointEnabledReadonly : 1; + unsigned _supla_int_t AuxMinMaxSetpointEnabledHidden : 1; + unsigned _supla_int_t UseSeparateHeatCoolOutputsReadonly : 1; + unsigned _supla_int_t UseSeparateHeatCoolOutputsHidden : 1; + unsigned _supla_int_t TemperaturesFreezeProtectionReadonly : 1; + unsigned _supla_int_t TemperaturesFreezeProtectionHidden : 1; + unsigned _supla_int_t TemperaturesEcoReadonly : 1; + unsigned _supla_int_t TemperaturesEcoHidden : 1; + unsigned _supla_int_t TemperaturesComfortReadonly : 1; + unsigned _supla_int_t TemperaturesComfortHidden : 1; + unsigned _supla_int_t TemperaturesBoostReadonly : 1; + unsigned _supla_int_t TemperaturesBoostHidden : 1; + unsigned _supla_int_t TemperaturesHeatProtectionReadonly : 1; + unsigned _supla_int_t TemperaturesHeatProtectionHidden : 1; + unsigned _supla_int_t TemperaturesHisteresisReadonly : 1; + unsigned _supla_int_t TemperaturesHisteresisHidden : 1; + unsigned _supla_int_t TemperaturesBelowAlarmReadonly : 1; + unsigned _supla_int_t TemperaturesBelowAlarmHidden : 1; + unsigned _supla_int_t TemperaturesAboveAlarmReadonly : 1; + unsigned _supla_int_t TemperaturesAboveAlarmHidden : 1; + unsigned _supla_int_t TemperaturesAuxMinSetpointReadonly : 1; + unsigned _supla_int_t TemperaturesAuxMinSetpointHidden : 1; + unsigned _supla_int_t TemperaturesAuxMaxSetpointReadonly : 1; + unsigned _supla_int_t TemperaturesAuxMaxSetpointHidden : 1; + unsigned _supla_int_t MasterThermostatChannelNoReadonly : 1; + unsigned _supla_int_t MasterThermostatChannelNoHidden : 1; + unsigned _supla_int_t HeatOrColdSourceSwitchReadonly : 1; + unsigned _supla_int_t HeatOrColdSourceSwitchHidden : 1; + unsigned _supla_int_t PumpSwitchReadonly : 1; + unsigned _supla_int_t PumpSwitchHidden : 1; + unsigned _supla_int_t Reserved : 12; +} HvacParameterFlags; + typedef struct { union { _supla_int_t MainThermometerChannelId; @@ -3062,7 +3197,35 @@ typedef struct { // cool mode selection, or they can use separate outputs - one for heating // and one for cooling unsigned char UseSeparateHeatCoolOutputs; // 0 - off (default), 1 - on - unsigned char Reserved[48]; + HvacParameterFlags ParameterFlags; + + union { + _supla_int_t MasterThermostatChannelId; + struct { + unsigned char MasterThermostatIsSet; // 0 - no; 1 - yes + unsigned char MasterThermostatChannelNo; + }; // v. >= 25 + }; + + union { + _supla_int_t HeatOrColdSourceSwitchChannelId; + struct { + unsigned char HeatOrColdSourceSwitchIsSet; // 0 - no; 1 - yes + unsigned char HeatOrColdSourceSwitchChannelNo; + }; // v. >= 25 + }; + + union { + _supla_int_t PumpSwitchChannelId; + struct { + unsigned char PumpSwitchIsSet; // 0 - no; 1 - yes + unsigned char PumpSwitchChannelNo; + }; // v. >= 25 + }; + + unsigned char Reserved[48 - sizeof(HvacParameterFlags) - + sizeof(_supla_int_t) - sizeof(_supla_int_t) - + sizeof(_supla_int_t)]; THVACTemperatureCfg Temperatures; } TChannelConfig_HVAC; // v. >= 21 @@ -3215,6 +3378,60 @@ typedef struct { unsigned char Reserved[32]; } TChannelConfig_ElectricityMeter; // v. >= 23 +typedef struct { + _supla_int_t PricePerUnit; // * 0.0001 + // Currency Code A https://www.nationsonline.org/oneworld/currencies.htm + char Currency[3]; + char CustomUnit[9]; // UTF8 including the terminating null byte ('\0') + + _supla_int_t ImpulsesPerUnit; + _supla_int64_t InitialValue; // 0.001 units + unsigned char AddToHistory; // 0 - False, 1 - True + + unsigned char Reserved[32]; +} TChannelConfig_ImpulseCounter; // v. >= 25 + +#define SUPLA_OCR_AUTHKEY_SIZE 33 + +#define OCR_LIGHTING_MODE_OFF (1ULL << 0) +#define OCR_LIGHTING_MODE_ALWAYS_ON (1ULL << 1) +#define OCR_LIGHTING_MODE_AUTO (1ULL << 2) + +typedef struct { + char AuthKey[SUPLA_OCR_AUTHKEY_SIZE]; // Set by the server. Alphanumeric null + // terminated string. + char Host[SUPLA_URL_HOST_MAXSIZE]; // Set by the server. Including the + // terminating null byte ('\0'). + + unsigned _supla_int_t + PhotoIntervalSec; // 0 - Disabled. The server may discard the + // value if it considers the frequency to be + // too high or too low. The server can set + // the accepted value. + unsigned _supla_int64_t LightingMode; // OCR_LIGHTING_MODE * + unsigned char LightingLevel; // 1-100% + unsigned _supla_int64_t MaximumIncrement; // Maximum impulse increment + // between shots. 0 == Unspecified + + // readonly, device capabilities + unsigned _supla_int64_t AvailableLightingModes; + unsigned char Reserved[128]; +} TChannelConfig_OCR; // v. >= 25 + +typedef struct { + // If OvercurrentMaxAllowed == 0, then overcurrent settings are not available. + // If OvercurrentThreshold == 0, then overcurrent protection is disabled. + unsigned _supla_int_t OvercurrentThreshold; // in 0.01 A + unsigned _supla_int_t OvercurrentMaxAllowed; // in 0.01 A, readonly + unsigned char DefaultRelatedMeterIsSet; // readonly, 1 - true, 0 - false + unsigned char + DefaultRelatedMeterChannelNo; // readonly, provides channel number of + // related meter if RelatedMeterIsSet + unsigned char Reserved[32]; +} TChannelConfig_PowerSwitch; // v. >= 25 + +typedef TChannelConfig_PowerSwitch TChannelConfig_LightSwitch; + typedef struct { _supla_int_t ChannelID; union { @@ -3290,6 +3507,26 @@ typedef struct { SUPLA_PN_BODY_MAXSIZE]; // Last variable in struct! } TDS_PushNotification; +#define SUPLA_SUBDEVICE_PRODUCT_CODE_MAXSIZE 51 +#define SUPLA_SUBDEVICE_SERIAL_NUMBER_MAXSIZE 51 + +typedef struct { + // device -> server + unsigned char SubDeviceId; + + char Name[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 - 201 B including the + // terminating null byte ('\0'). + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; // 21 B including the terminating null + // byte ('\0'). + char ProductCode[SUPLA_SUBDEVICE_PRODUCT_CODE_MAXSIZE]; // 51 B including the + // terminating null + // byte ('\0'). + char + SerialNumber[SUPLA_SUBDEVICE_SERIAL_NUMBER_MAXSIZE]; // 51 B including + // the terminating + // null byte ('\0'). +} TDS_SubdeviceDetails; + #define SUPLA_PN_CLIENT_TOKEN_MAXSIZE 256 #define PLATFORM_UNKNOWN 0 #define PLATFORM_IOS 1 diff --git a/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/supla-client.h b/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/supla-client.h index 2a39bc27..39249aca 100644 --- a/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/supla-client.h +++ b/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/Headers/supla-client.h @@ -320,9 +320,13 @@ _supla_int_t srpc_evtool_value_get(TSuplaChannelExtendedValue *ev, unsigned short index, TSuplaChannelExtendedValue *dest); -_supla_int_t srpc_evtool_v2_extended2emextended( +_supla_int_t srpc_evtool_v3_extended2emextended( const TSuplaChannelExtendedValue *ev, - TElectricityMeter_ExtendedValue_V2 *em_ev); + TElectricityMeter_ExtendedValue_V3 *em_ev); + +_supla_int_t srpc_evtool_extended2emextended( + const TSuplaChannelExtendedValue *ev, + TElectricityMeter_ExtendedValue_V3 *em_ev); _supla_int_t srpc_evtool_v1_extended2icextended( const TSuplaChannelExtendedValue *ev, diff --git a/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/libsupla-client-iphoneos.a b/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/libsupla-client-iphoneos.a index b365e863..ba2d894f 100644 Binary files a/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/libsupla-client-iphoneos.a and b/LibSuplaClient.xcframework/ios-arm64_armv7_armv7s/libsupla-client-iphoneos.a differ diff --git a/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/proto.h b/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/proto.h index ace5d5dc..16115bd9 100644 --- a/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/proto.h +++ b/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/proto.h @@ -119,7 +119,7 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; // CS - client -> server // SC - server -> client -#define SUPLA_PROTO_VERSION 24 +#define SUPLA_PROTO_VERSION 25 #define SUPLA_PROTO_VERSION_MIN 1 #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO) || defined(SUPLA_DEVICE) @@ -129,7 +129,9 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; // SUPLA_MAX_DATA_SIZE should be bigger then calcfg, device config, channel // config MAXSIZE. Otherwise sending will fail #define SUPLA_MAX_DATA_SIZE 600 // Registration header without channels +#define USE_DEPRECATED_EMEV_V2 // Temporary. It will be removed. #elif defined(ESP8266) +#define USE_DEPRECATED_EMEV_V2 // Temporary. It will be removed. // supla-espressif-esp compilations #define SUPLA_MAX_DATA_SIZE 1536 #else @@ -296,6 +298,7 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CS_CALL_SET_CHANNEL_CONFIG 1220 // ver. >= 21 #define SUPLA_CS_CALL_GET_DEVICE_CONFIG 1240 // ver. >= 21 #define SUPLA_SC_CALL_DEVICE_CONFIG_UPDATE_OR_RESULT 1250 // ver. >= 21 +#define SUPLA_DS_CALL_SET_SUBDEVICE_DETAILS 1260 // ver. >= 25 #define SUPLA_RESULT_RESPONSE_TIMEOUT -8 #define SUPLA_RESULT_CANT_CONNECT_TO_HOST -7 @@ -351,6 +354,9 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_RESULTCODE_NOT_REGISTERED 39 // ver. >= 20 #define SUPLA_RESULTCODE_DENY_CHANNEL_IS_ASSOCIETED_WITH_VBT 40 // >= 20 #define SUPLA_RESULTCODE_DENY_CHANNEL_IS_ASSOCIETED_WITH_PUSH 41 // >= 20 +#define SUPLA_RESULTCODE_RESTART_REQUESTED 42 // ver. >= 25 +#define SUPLA_RESULTCODE_IDENTIFY_REQUESTED 43 // ver. >= 25 +#define SUPLA_RESULTCODE_MALFORMED_EMAIL 44 // ver. >= ? #define SUPLA_OAUTH_RESULTCODE_ERROR 0 // ver. >= 10 #define SUPLA_OAUTH_RESULTCODE_SUCCESS 1 // ver. >= 10 @@ -485,6 +491,8 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNELFNC_CURTAIN 930 // ver. >= 24 #define SUPLA_CHANNELFNC_VERTICAL_BLIND 940 // ver. >= 24 #define SUPLA_CHANNELFNC_ROLLER_GARAGE_DOOR 950 // ver. >= 24 +#define SUPLA_CHANNELFNC_PUMPSWITCH 960 // ver. >= 25 +#define SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH 970 // ver. >= 25 #define SUPLA_BIT_FUNC_CONTROLLINGTHEGATEWAYLOCK 0x00000001 #define SUPLA_BIT_FUNC_CONTROLLINGTHEGATE 0x00000002 @@ -512,6 +520,8 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_BIT_FUNC_CURTAIN 0x00800000 // ver. >= 24 #define SUPLA_BIT_FUNC_VERTICAL_BLIND 0x01000000 // ver. >= 24 #define SUPLA_BIT_FUNC_ROLLER_GARAGE_DOOR 0x02000000 // ver. >= 24 +#define SUPLA_BIT_FUNC_PUMPSWITCH 0x04000000 // ver. >= 25 +#define SUPLA_BIT_FUNC_HEATORCOLDSOURCESWITCH 0x08000000 // ver. >= 25 #define SUPLA_EVENT_CONTROLLINGTHEGATEWAYLOCK 10 #define SUPLA_EVENT_CONTROLLINGTHEGATE 20 @@ -559,13 +569,20 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_MFR_POLIER 15 #define SUPLA_MFR_ERGO_ENERGIA 16 #define SUPLA_MFR_SOMEF 17 +#define SUPLA_MFR_AURATON 18 // BIT map definition for TDS_SuplaRegisterDevice_*::Flags (32 bit) -#define SUPLA_DEVICE_FLAG_CALCFG_ENTER_CFG_MODE 0x0010 // ver. >= 17 -#define SUPLA_DEVICE_FLAG_SLEEP_MODE_ENABLED 0x0020 // ver. >= 18 -#define SUPLA_DEVICE_FLAG_CALCFG_SET_TIME 0x0040 // ver. >= 21 -#define SUPLA_DEVICE_FLAG_DEVICE_CONFIG_SUPPORTED 0x0080 // ver. >= 21 -#define SUPLA_DEVICE_FLAG_DEVICE_LOCKED 0x0100 // ver. >= 22 +#define SUPLA_DEVICE_FLAG_CALCFG_ENTER_CFG_MODE 0x0010 // ver. >= 17 +#define SUPLA_DEVICE_FLAG_SLEEP_MODE_ENABLED 0x0020 // ver. >= 18 +#define SUPLA_DEVICE_FLAG_CALCFG_SET_TIME 0x0040 // ver. >= 21 +#define SUPLA_DEVICE_FLAG_DEVICE_CONFIG_SUPPORTED 0x0080 // ver. >= 21 +#define SUPLA_DEVICE_FLAG_DEVICE_LOCKED 0x0100 // ver. >= 22 +#define SUPLA_DEVICE_FLAG_CALCFG_SUBDEVICE_PAIRING 0x0200 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_CALCFG_IDENTIFY_DEVICE 0x0400 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_CALCFG_RESTART_DEVICE 0x0800 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_ALWAYS_ALLOW_CHANNEL_DELETION 0x1000 // ver. >= 25 +#define SUPLA_DEVICE_FLAG_BLOCK_ADDING_CHANNELS_AFTER_DELETION \ + 0x2000 // ver. >= 25 // BIT map definition for TDS_SuplaRegisterDevice_F::ConfigFields (64 bit) // type: TDeviceConfig_StatusLed @@ -586,6 +603,8 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; // type: TDeviceConfig_HomeScreenOffDelayType #define SUPLA_DEVICE_CONFIG_FIELD_HOME_SCREEN_OFF_DELAY_TYPE \ (1ULL << 7) // v. >= 24 +// type: TDeviceConfig_PowerStatusLed +#define SUPLA_DEVICE_CONFIG_FIELD_POWER_STATUS_LED (1ULL << 8) // v. >= 25 // BIT map definition for TDS_SuplaDeviceChannel_C::Flags (32 bit) #define SUPLA_CHANNEL_FLAG_ZWAVE_BRIDGE 0x0001 // ver. >= 12 @@ -604,10 +623,10 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNEL_FLAG_RS_SBS_AND_STOP_ACTIONS 0x0080 // ver. >= 17 #define SUPLA_CHANNEL_FLAG_RGBW_COMMANDS_SUPPORTED 0x0100 // ver. >= 21 // Free bits for future use: 0x0200, 0x0400, 0x0800 -#define SUPLA_CHANNEL_FLAG_RS_AUTO_CALIBRATION 0x1000 // ver. >= 15 -#define SUPLA_CHANNEL_FLAG_CALCFG_RESET_COUNTERS 0x2000 // ver. >= 15 -#define SUPLA_CHANNEL_FLAG_CALCFG_RECALIBRATE 0x4000 // ver. >= 15 -// Free bits for future use: 0x8000 +#define SUPLA_CHANNEL_FLAG_RS_AUTO_CALIBRATION 0x1000 // ver. >= 15 +#define SUPLA_CHANNEL_FLAG_CALCFG_RESET_COUNTERS 0x2000 // ver. >= 15 +#define SUPLA_CHANNEL_FLAG_CALCFG_RECALIBRATE 0x4000 // ver. >= 15 +#define SUPLA_CHANNEL_FLAG_CALCFG_IDENTIFY_SUBDEVICE 0x8000 // ver. >= 25 #define SUPLA_CHANNEL_FLAG_CHANNELSTATE 0x00010000 // ver. >= 12 #define SUPLA_CHANNEL_FLAG_PHASE1_UNSUPPORTED 0x00020000 // ver. >= 12 #define SUPLA_CHANNEL_FLAG_PHASE2_UNSUPPORTED 0x00040000 // ver. >= 12 @@ -622,9 +641,10 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNEL_FLAG_POSSIBLE_SLEEP_MODE_deprecated \ 0x04000000 // ver. >= 12 DEPRECATED #define SUPLA_CHANNEL_FLAG_RUNTIME_CHANNEL_CONFIG_UPDATE \ - 0x08000000 // ver. >= 21 -#define SUPLA_CHANNEL_FLAG_WEEKLY_SCHEDULE 0x10000000 // ver. >= 21 -#define SUPLA_CHANNEL_FLAG_HAS_PARENT 0x20000000 // ver. >= 21 + 0x08000000 // ver. >= 21 +#define SUPLA_CHANNEL_FLAG_WEEKLY_SCHEDULE 0x10000000 // ver. >= 21 +#define SUPLA_CHANNEL_FLAG_HAS_PARENT 0x20000000 // ver. >= 21 +#define SUPLA_CHANNEL_FLAG_CALCFG_RESTART_SUBDEVICE 0x40000000 // ver. >= 25 #pragma pack(push, 1) typedef struct { @@ -702,7 +722,10 @@ typedef struct { #ifdef USE_DEPRECATED_EMEV_V1 #define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V1 10 #endif /*USE_DEPRECATED_EMEV_V1*/ +#ifdef USE_DEPRECATED_EMEV_V2 #define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V2 12 +#endif /*USE_DEPRECATED_EMEV_V2*/ +#define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V3 14 #define EV_TYPE_IMPULSE_COUNTER_DETAILS_V1 20 #define EV_TYPE_THERMOSTAT_DETAILS_V1 30 #define EV_TYPE_CHANNEL_STATE_V1 40 @@ -822,7 +845,7 @@ typedef struct { #define SUPLA_HVAC_MODE_CMD_SWITCH_TO_MANUAL 10 typedef struct { - unsigned char IsOn; // DS: 0/1 (or 0..100 ?) + unsigned char IsOn; // DS: 0/1 (for on/off) or 2..102 (for 0-100%) unsigned char Mode; // SUPLA_HVAC_MODE_ _supla_int16_t SetpointTemperatureHeat; // * 0.01 Celcius degree - used for heating @@ -905,7 +928,7 @@ typedef struct { unsigned char DefaultIcon; unsigned char SubDeviceId; // 0 - no subdevice, 1..255 - subdevice id -} TDS_SuplaDeviceChannel_E; // ver. >= 25 +} TDS_SuplaDeviceChannel_E; // ver. >= 25 typedef struct { // device -> server @@ -1067,9 +1090,8 @@ typedef struct { unsigned _supla_int16_t channel_report_size; unsigned char channel_report - [CHANNEL_REPORT_MAXSIZE]; // One byte per channel. The meaning of the - // bits is determined by CHANNEL_REPORT_*. - + [CHANNEL_REPORT_MAXSIZE]; // One byte per channel. The meaning of the + // bits is determined by CHANNEL_REPORT_*. } TSD_SuplaRegisterDeviceResult_B; // ver. >= 25 typedef struct { @@ -1427,6 +1449,10 @@ typedef struct { #define CHANNEL_RELATION_TYPE_AUX_THERMOMETER_GENERIC_HEATER 7 #define CHANNEL_RELATION_TYPE_AUX_THERMOMETER_GENERIC_COOLER 8 +#define CHANNEL_RELATION_TYPE_MASTER_THERMOSTAT 20 +#define CHANNEL_RELATION_TYPE_HEAT_OR_COLD_SOURCE_SWITCH 21 +#define CHANNEL_RELATION_TYPE_PUMP_SWITCH 22 + typedef struct { char EOL; // End Of List _supla_int_t Id; @@ -1538,9 +1564,9 @@ typedef struct { } TAction_ShadingSystem_Parameters; // ver. >= 19 typedef struct { - char Brightness; // -1 == Ignore - char ColorBrightness; // -1 == Ignore - unsigned int Color; // 0 == Ignore + char Brightness; // -1 == Ignore + char ColorBrightness; // -1 == Ignore + unsigned _supla_int_t Color; // 0 == Ignore char ColorRandom; char OnOff; char Reserved[8]; @@ -1810,10 +1836,10 @@ typedef struct { #define EM_VAR_FORWARD_ACTIVE_ENERGY_BALANCED 0x2000 #define EM_VAR_REVERSE_ACTIVE_ENERGY_BALANCED 0x4000 -#define EM_VAR_VOLTAGE_PHASE_ANGLE_12 0x10000 // ver. >= 22 -#define EM_VAR_VOLTAGE_PHASE_ANGLE_13 0x20000 // ver. >= 22 -#define EM_VAR_VOLTAGE_PHASE_SEQUENCE 0x40000 // ver. >= 22 -#define EM_VAR_CURRENT_PHASE_SEQUENCE 0x80000 // ver. >= 22 +#define EM_VAR_VOLTAGE_PHASE_ANGLE_12 0x10000 // ver. >= 25 +#define EM_VAR_VOLTAGE_PHASE_ANGLE_13 0x20000 // ver. >= 25 +#define EM_VAR_VOLTAGE_PHASE_SEQUENCE 0x40000 // ver. >= 25 +#define EM_VAR_CURRENT_PHASE_SEQUENCE 0x80000 // ver. >= 25 #define EM_VAR_POWER_ACTIVE_KW 0x100000 #define EM_VAR_POWER_REACTIVE_KVAR 0x200000 @@ -1852,6 +1878,7 @@ typedef struct { } TElectricityMeter_ExtendedValue; // v. >= 10 #endif /*USE_DEPRECATED_EMEV_V1*/ +#ifdef USE_DEPRECATED_EMEV_V2 // [IODevice->Server->Client] typedef struct { unsigned _supla_int64_t total_forward_active_energy[3]; // * 0.00001 kWh @@ -1879,6 +1906,7 @@ typedef struct { TElectricityMeter_Measurement m[EM_MEASUREMENT_COUNT]; // Last variable in // struct! } TElectricityMeter_ExtendedValue_V2; // v. >= 12 +#endif /*USE_DEPRECATED_EMEV_V2*/ // [IODevice->Server->Client] typedef struct { @@ -1915,7 +1943,7 @@ typedef struct { _supla_int_t m_count; TElectricityMeter_Measurement m[EM_MEASUREMENT_COUNT]; // Last variable in // struct! -} TElectricityMeter_ExtendedValue_V3; // v. >= 22 +} TElectricityMeter_ExtendedValue_V3; // v. >= 25 #define EM_VALUE_FLAG_PHASE1_ON 0x01 #define EM_VALUE_FLAG_PHASE2_ON 0x02 @@ -2065,6 +2093,12 @@ typedef struct { #define SUPLA_CALCFG_CMD_RECALIBRATE 8000 // v. >= 15 #define SUPLA_CALCFG_CMD_ENTER_CFG_MODE 9000 // v. >= 17 #define SUPLA_CALCFG_CMD_SET_TIME 9100 // v. >= 21 +#define SUPLA_CALCFG_CMD_START_SUBDEVICE_PAIRING 9200 // v. >= 25 +#define SUPLA_CALCFG_CMD_IDENTIFY_DEVICE 9300 // v. >= 25 +#define SUPLA_CALCFG_CMD_IDENTIFY_SUBDEVICE 9310 // v. >= 25 +#define SUPLA_CALCFG_CMD_RESTART_DEVICE 9400 // v. >= 25 +#define SUPLA_CALCFG_CMD_RESTART_SUBDEVICE 9410 // v. >= 25 +#define SUPLA_CALCFG_CMD_TAKE_OCR_PHOTO 9420 // v. >= 25 #define SUPLA_CALCFG_DATATYPE_RS_SETTINGS 1000 #define SUPLA_CALCFG_DATATYPE_FB_SETTINGS 1100 // v. >= 17 @@ -2116,6 +2150,33 @@ typedef struct { unsigned _supla_int16_t LightSourceLifespan; // 0 - 65535 hours } TCalCfg_LightSourceLifespan; +#define SUPLA_CALCFG_SUBDEVICE_NAME_MAXSIZE 120 + +// Subdevice Pairing result is send in TDS_DeviceCalCfgResult. Possible values: +// SUPLA_CALCFG_RESULT_TRUE - paring result/status is in Data +// SUPLA_CALCFG_RESULT_UNAUTHORIZED - unauthorized +// SUPLA_CALCFG_RESULT_NOT_SUPPORTED - not supported +// Only in case of TRUE, TCalCfg_SubdevicePairingResult is included in +// TDS_DeviceCalCfgResult. +#define SUPLA_CALCFG_PAIRINGRESULT_PROCEDURE_STARTED 0 +#define SUPLA_CALCFG_PAIRINGRESULT_ONGOING 1 +#define SUPLA_CALCFG_PAIRINGRESULT_NO_NEW_DEVICE_FOUND 2 +#define SUPLA_CALCFG_PAIRINGRESULT_SUCCESS 3 +#define SUPLA_CALCFG_PAIRINGRESULT_DEVICE_NOT_SUPPORTED 4 +#define SUPLA_CALCFG_PAIRINGRESULT_RESOURCES_LIMIT_EXCEEDED 5 +#define SUPLA_CALCFG_PAIRINGRESULT_NOT_STARTED_NOT_READY 6 +#define SUPLA_CALCFG_PAIRINGRESULT_NOT_STARTED_BUSY 7 + +typedef struct { + unsigned _supla_int16_t + ElapsedTimeSec; // Time in seconds since procedure was started + unsigned _supla_int16_t MaximumDurationSec; // Time in seconds + unsigned char PairingResult; // SUPLA_CALCFG_PAIRINGRESULT_ + unsigned char NameSize; // including the terminating null byte ('\0') + char Name[SUPLA_CALCFG_SUBDEVICE_NAME_MAXSIZE]; // UTF8. Last variable in + // struct! +} TCalCfg_SubdevicePairingResult; // v. >= 25 + // CALCFG == CALIBRATION / CONFIG typedef struct { _supla_int_t ChannelID; @@ -2284,7 +2345,7 @@ typedef struct { TThermostatValueGroup Group[4]; } TThermostat_ScheduleCfg; // v. >= 11 -// Tempeature definitions for Heatpol thermostat +// Temperature definitions for Heatpol thermostat // TThermostatTemperatureCfg #define TEMPERATURE_INDEX1 0x0001 #define TEMPERATURE_INDEX2 0x0002 @@ -2304,7 +2365,7 @@ typedef struct { unsigned _supla_int16_t Temperature[10]; } TThermostatTemperatureCfg; -// Tempeature definitions for HVAC +// Temperature definitions for HVAC // THVACTemperatureCfg // Below values are settable by user in UI // Temperature below which heating will be enabled as a freeze protection @@ -2445,8 +2506,14 @@ typedef struct { #define SUPLA_CHANNELSTATE_FIELD_BATTERYHEALTH 0x0200 #define SUPLA_CHANNELSTATE_FIELD_BRIDGENODEONLINE 0x0400 #define SUPLA_CHANNELSTATE_FIELD_LASTCONNECTIONRESETCAUSE 0x0800 +// LIGHTSOURCELIFESPAN, LIGHTSOURCEOPERATINGTIME, and OPERATINGTIME are +// mutually exclusive. Use only one of them. #define SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCELIFESPAN 0x1000 #define SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCEOPERATINGTIME 0x2000 +#define SUPLA_CHANNELSTATE_FIELD_OPERATINGTIME 0x4000 +// SWITCHCYCLECOUNT and defualtIconField are is mutually exclusive. Use only one +// of them. +#define SUPLA_CHANNELSTATE_FIELD_SWITCHCYCLECOUNT 0x8000 #define SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN 0 #define SUPLA_LASTCONNECTIONRESETCAUSE_ACTIVITY_TIMEOUT 1 @@ -2460,8 +2527,11 @@ typedef struct { _supla_int_t ChannelID; // Server -> Client unsigned char ChannelNumber; // Device -> Server }; - _supla_int_t Fields; - _supla_int_t defaultIconField; // SUPLA_CHANNELSTATE_FIELD_* + _supla_int_t Fields; // SUPLA_CHANNELSTATE_FIELD_* + union { + _supla_int_t defaultIconField; + unsigned _supla_int_t SwitchCycleCount; + }; unsigned _supla_int_t IPv4; unsigned char MAC[6]; unsigned char BatteryLevel; // 0 - 100% @@ -2479,6 +2549,7 @@ typedef struct { _supla_int16_t LightSourceLifespanLeft; // -327,67 - 100.00% // LightSourceLifespan * 0.01 _supla_int_t LightSourceOperatingTime; // -3932100sec. - 3932100sec. + unsigned _supla_int_t OperatingTime; // time in seconds }; char EmptySpace[2]; // Empty space for future use } TDSC_ChannelState; // v. >= 12 Device -> Server -> Client @@ -2581,11 +2652,11 @@ typedef struct { } TSC_SetRegistrationEnabledResult; // v. >= 12 typedef struct { - int DeviceID; + _supla_int_t DeviceID; } TCS_DeviceReconnectRequest; // v. >= 12 typedef struct { - int DeviceID; + _supla_int_t DeviceID; unsigned char ResultCode; } TSC_DeviceReconnectRequestResult; // v. >= 12 @@ -2618,6 +2689,7 @@ typedef struct { // For SUPLA_CHANNELFNC_HVAC_THERMOSTAT, ALT weekly schedule is used for // cooling subfuction, while standard weelkly schedule is used for heating #define SUPLA_CONFIG_TYPE_ALT_WEEKLY_SCHEDULE 3 +#define SUPLA_CONFIG_TYPE_OCR 4 /******************************************** * DEVICE CONFIG STRUCTURES @@ -2677,6 +2749,10 @@ typedef struct { unsigned char StatusLedType; // SUPLA_DEVCFG_STATUS_LED_ } TDeviceConfig_StatusLed; // v. >= 21 +typedef struct { + unsigned char Disabled; // 1 - true; 0 - false +} TDeviceConfig_PowerStatusLed; // v. >= 25 + typedef struct { unsigned char ScreenBrightness; // 0-100% unsigned char Automatic; // 0 - false; 1 - true @@ -2720,6 +2796,7 @@ typedef struct { #define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_TIME_DATE (1ULL << 4) #define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_TEMPERATURE_TIME (1ULL << 5) #define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_MAIN_AND_AUX_TEMPERATURE (1ULL << 6) +#define SUPLA_DEVCFG_HOME_SCREEN_CONTENT_MODE_OR_TEMPERATURE (1ULL << 7) typedef struct { // bit field with all available modes (reported by device, readonly for other @@ -2933,6 +3010,7 @@ typedef struct { #define SUPLA_HVAC_ALGORITHM_NOT_SET 0 #define SUPLA_HVAC_ALGORITHM_ON_OFF_SETPOINT_MIDDLE (1ULL << 0) #define SUPLA_HVAC_ALGORITHM_ON_OFF_SETPOINT_AT_MOST (1ULL << 1) +#define SUPLA_HVAC_ALGORITHM_PID (1ULL << 2) // HVAC channel validation rules for thermometers: // - MainThermometerChannelNo must be set @@ -3015,6 +3093,63 @@ typedef struct { #define SUPLA_HVAC_SUBFUNCTION_HEAT 1 #define SUPLA_HVAC_SUBFUNCTION_COOL 2 +typedef struct { + unsigned _supla_int_t MainThermometerChannelNoReadonly : 1; + unsigned _supla_int_t MainThermometerChannelNoHidden : 1; + unsigned _supla_int_t AuxThermometerChannelNoReadonly : 1; + unsigned _supla_int_t AuxThermometerChannelNoHidden : 1; + unsigned _supla_int_t BinarySensorChannelNoReadonly : 1; + unsigned _supla_int_t BinarySensorChannelNoHidden : 1; + unsigned _supla_int_t AuxThermometerTypeReadonly : 1; + unsigned _supla_int_t AuxThermometerTypeHidden : 1; + unsigned _supla_int_t AntiFreezeAndOverheatProtectionEnabledReadonly : 1; + unsigned _supla_int_t AntiFreezeAndOverheatProtectionEnabledHidden : 1; + unsigned _supla_int_t UsedAlgorithmReadonly : 1; + unsigned _supla_int_t UsedAlgorithmHidden : 1; + unsigned _supla_int_t MinOnTimeSReadonly : 1; + unsigned _supla_int_t MinOnTimeSHidden : 1; + unsigned _supla_int_t MinOffTimeSReadonly : 1; + unsigned _supla_int_t MinOffTimeSHidden : 1; + unsigned _supla_int_t OutputValueOnErrorReadonly : 1; + unsigned _supla_int_t OutputValueOnErrorHidden : 1; + unsigned _supla_int_t SubfunctionReadonly : 1; + unsigned _supla_int_t SubfunctionHidden : 1; + unsigned _supla_int_t + TemperatureSetpointChangeSwitchesToManualModeReadonly : 1; + unsigned _supla_int_t TemperatureSetpointChangeSwitchesToManualModeHidden : 1; + unsigned _supla_int_t AuxMinMaxSetpointEnabledReadonly : 1; + unsigned _supla_int_t AuxMinMaxSetpointEnabledHidden : 1; + unsigned _supla_int_t UseSeparateHeatCoolOutputsReadonly : 1; + unsigned _supla_int_t UseSeparateHeatCoolOutputsHidden : 1; + unsigned _supla_int_t TemperaturesFreezeProtectionReadonly : 1; + unsigned _supla_int_t TemperaturesFreezeProtectionHidden : 1; + unsigned _supla_int_t TemperaturesEcoReadonly : 1; + unsigned _supla_int_t TemperaturesEcoHidden : 1; + unsigned _supla_int_t TemperaturesComfortReadonly : 1; + unsigned _supla_int_t TemperaturesComfortHidden : 1; + unsigned _supla_int_t TemperaturesBoostReadonly : 1; + unsigned _supla_int_t TemperaturesBoostHidden : 1; + unsigned _supla_int_t TemperaturesHeatProtectionReadonly : 1; + unsigned _supla_int_t TemperaturesHeatProtectionHidden : 1; + unsigned _supla_int_t TemperaturesHisteresisReadonly : 1; + unsigned _supla_int_t TemperaturesHisteresisHidden : 1; + unsigned _supla_int_t TemperaturesBelowAlarmReadonly : 1; + unsigned _supla_int_t TemperaturesBelowAlarmHidden : 1; + unsigned _supla_int_t TemperaturesAboveAlarmReadonly : 1; + unsigned _supla_int_t TemperaturesAboveAlarmHidden : 1; + unsigned _supla_int_t TemperaturesAuxMinSetpointReadonly : 1; + unsigned _supla_int_t TemperaturesAuxMinSetpointHidden : 1; + unsigned _supla_int_t TemperaturesAuxMaxSetpointReadonly : 1; + unsigned _supla_int_t TemperaturesAuxMaxSetpointHidden : 1; + unsigned _supla_int_t MasterThermostatChannelNoReadonly : 1; + unsigned _supla_int_t MasterThermostatChannelNoHidden : 1; + unsigned _supla_int_t HeatOrColdSourceSwitchReadonly : 1; + unsigned _supla_int_t HeatOrColdSourceSwitchHidden : 1; + unsigned _supla_int_t PumpSwitchReadonly : 1; + unsigned _supla_int_t PumpSwitchHidden : 1; + unsigned _supla_int_t Reserved : 12; +} HvacParameterFlags; + typedef struct { union { _supla_int_t MainThermometerChannelId; @@ -3062,7 +3197,35 @@ typedef struct { // cool mode selection, or they can use separate outputs - one for heating // and one for cooling unsigned char UseSeparateHeatCoolOutputs; // 0 - off (default), 1 - on - unsigned char Reserved[48]; + HvacParameterFlags ParameterFlags; + + union { + _supla_int_t MasterThermostatChannelId; + struct { + unsigned char MasterThermostatIsSet; // 0 - no; 1 - yes + unsigned char MasterThermostatChannelNo; + }; // v. >= 25 + }; + + union { + _supla_int_t HeatOrColdSourceSwitchChannelId; + struct { + unsigned char HeatOrColdSourceSwitchIsSet; // 0 - no; 1 - yes + unsigned char HeatOrColdSourceSwitchChannelNo; + }; // v. >= 25 + }; + + union { + _supla_int_t PumpSwitchChannelId; + struct { + unsigned char PumpSwitchIsSet; // 0 - no; 1 - yes + unsigned char PumpSwitchChannelNo; + }; // v. >= 25 + }; + + unsigned char Reserved[48 - sizeof(HvacParameterFlags) - + sizeof(_supla_int_t) - sizeof(_supla_int_t) - + sizeof(_supla_int_t)]; THVACTemperatureCfg Temperatures; } TChannelConfig_HVAC; // v. >= 21 @@ -3215,6 +3378,60 @@ typedef struct { unsigned char Reserved[32]; } TChannelConfig_ElectricityMeter; // v. >= 23 +typedef struct { + _supla_int_t PricePerUnit; // * 0.0001 + // Currency Code A https://www.nationsonline.org/oneworld/currencies.htm + char Currency[3]; + char CustomUnit[9]; // UTF8 including the terminating null byte ('\0') + + _supla_int_t ImpulsesPerUnit; + _supla_int64_t InitialValue; // 0.001 units + unsigned char AddToHistory; // 0 - False, 1 - True + + unsigned char Reserved[32]; +} TChannelConfig_ImpulseCounter; // v. >= 25 + +#define SUPLA_OCR_AUTHKEY_SIZE 33 + +#define OCR_LIGHTING_MODE_OFF (1ULL << 0) +#define OCR_LIGHTING_MODE_ALWAYS_ON (1ULL << 1) +#define OCR_LIGHTING_MODE_AUTO (1ULL << 2) + +typedef struct { + char AuthKey[SUPLA_OCR_AUTHKEY_SIZE]; // Set by the server. Alphanumeric null + // terminated string. + char Host[SUPLA_URL_HOST_MAXSIZE]; // Set by the server. Including the + // terminating null byte ('\0'). + + unsigned _supla_int_t + PhotoIntervalSec; // 0 - Disabled. The server may discard the + // value if it considers the frequency to be + // too high or too low. The server can set + // the accepted value. + unsigned _supla_int64_t LightingMode; // OCR_LIGHTING_MODE * + unsigned char LightingLevel; // 1-100% + unsigned _supla_int64_t MaximumIncrement; // Maximum impulse increment + // between shots. 0 == Unspecified + + // readonly, device capabilities + unsigned _supla_int64_t AvailableLightingModes; + unsigned char Reserved[128]; +} TChannelConfig_OCR; // v. >= 25 + +typedef struct { + // If OvercurrentMaxAllowed == 0, then overcurrent settings are not available. + // If OvercurrentThreshold == 0, then overcurrent protection is disabled. + unsigned _supla_int_t OvercurrentThreshold; // in 0.01 A + unsigned _supla_int_t OvercurrentMaxAllowed; // in 0.01 A, readonly + unsigned char DefaultRelatedMeterIsSet; // readonly, 1 - true, 0 - false + unsigned char + DefaultRelatedMeterChannelNo; // readonly, provides channel number of + // related meter if RelatedMeterIsSet + unsigned char Reserved[32]; +} TChannelConfig_PowerSwitch; // v. >= 25 + +typedef TChannelConfig_PowerSwitch TChannelConfig_LightSwitch; + typedef struct { _supla_int_t ChannelID; union { @@ -3290,6 +3507,26 @@ typedef struct { SUPLA_PN_BODY_MAXSIZE]; // Last variable in struct! } TDS_PushNotification; +#define SUPLA_SUBDEVICE_PRODUCT_CODE_MAXSIZE 51 +#define SUPLA_SUBDEVICE_SERIAL_NUMBER_MAXSIZE 51 + +typedef struct { + // device -> server + unsigned char SubDeviceId; + + char Name[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 - 201 B including the + // terminating null byte ('\0'). + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; // 21 B including the terminating null + // byte ('\0'). + char ProductCode[SUPLA_SUBDEVICE_PRODUCT_CODE_MAXSIZE]; // 51 B including the + // terminating null + // byte ('\0'). + char + SerialNumber[SUPLA_SUBDEVICE_SERIAL_NUMBER_MAXSIZE]; // 51 B including + // the terminating + // null byte ('\0'). +} TDS_SubdeviceDetails; + #define SUPLA_PN_CLIENT_TOKEN_MAXSIZE 256 #define PLATFORM_UNKNOWN 0 #define PLATFORM_IOS 1 diff --git a/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/supla-client.h b/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/supla-client.h index 2a39bc27..39249aca 100644 --- a/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/supla-client.h +++ b/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/Headers/supla-client.h @@ -320,9 +320,13 @@ _supla_int_t srpc_evtool_value_get(TSuplaChannelExtendedValue *ev, unsigned short index, TSuplaChannelExtendedValue *dest); -_supla_int_t srpc_evtool_v2_extended2emextended( +_supla_int_t srpc_evtool_v3_extended2emextended( const TSuplaChannelExtendedValue *ev, - TElectricityMeter_ExtendedValue_V2 *em_ev); + TElectricityMeter_ExtendedValue_V3 *em_ev); + +_supla_int_t srpc_evtool_extended2emextended( + const TSuplaChannelExtendedValue *ev, + TElectricityMeter_ExtendedValue_V3 *em_ev); _supla_int_t srpc_evtool_v1_extended2icextended( const TSuplaChannelExtendedValue *ev, diff --git a/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/libsupla-client-iphonesimulator.a b/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/libsupla-client-iphonesimulator.a index 13127ab4..5d4266eb 100644 Binary files a/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/libsupla-client-iphonesimulator.a and b/LibSuplaClient.xcframework/ios-arm64_x86_64-simulator/libsupla-client-iphonesimulator.a differ diff --git a/SUPLA.xcodeproj/project.pbxproj b/SUPLA.xcodeproj/project.pbxproj index 5f441c68..5671b407 100644 --- a/SUPLA.xcodeproj/project.pbxproj +++ b/SUPLA.xcodeproj/project.pbxproj @@ -494,6 +494,8 @@ A5477DCB2AA5EC4000220B4A /* CreateChannelWithChildrenUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5477DCA2AA5EC4000220B4A /* CreateChannelWithChildrenUseCase.swift */; }; A5477DCD2AA5F7A900220B4A /* IssueIconType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5477DCC2AA5F7A900220B4A /* IssueIconType.swift */; }; A5477DCF2AA616FD00220B4A /* CellButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5477DCE2AA616FD00220B4A /* CellButton.swift */; }; + A547821F2CA2B4D400CD3010 /* UiSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A547821E2CA2B4BB00CD3010 /* UiSettings.swift */; }; + A54782212CA2C13400CD3010 /* View+ScaleFactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54782202CA2C12E00CD3010 /* View+ScaleFactor.swift */; }; A54A064A2AF3DF6B00C03DBC /* SessionResponseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54A06492AF3DF6B00C03DBC /* SessionResponseProvider.swift */; }; A54A064C2AF3E55700C03DBC /* SuplaSchedulers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54A064B2AF3E55700C03DBC /* SuplaSchedulers.swift */; }; A54A064F2AF3ED6200C03DBC /* RequestHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54A064E2AF3ED6200C03DBC /* RequestHelperTests.swift */; }; @@ -714,6 +716,19 @@ A58B2CB42AC195FA00764388 /* CreateTemperaturesListUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58B2CB32AC195FA00764388 /* CreateTemperaturesListUseCaseTests.swift */; }; A58B2CB62AC197E300764388 /* ChannelBaseUseCasesMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58B2CB52AC197E300764388 /* ChannelBaseUseCasesMocks.swift */; }; A58D98C82AC1A3AD00DCB526 /* SetChannelConfigUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58D98C72AC1A3AD00DCB526 /* SetChannelConfigUseCaseTests.swift */; }; + A594ADC02C9C07C600780EF3 /* ThermostatIndicatorIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADBF2C9C07C600780EF3 /* ThermostatIndicatorIcon.swift */; }; + A594ADC32C9C0A9500780EF3 /* ThermostatIndicatorIconTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADC22C9C0A9500780EF3 /* ThermostatIndicatorIconTests.swift */; }; + A594ADC52C9C19E900780EF3 /* ListOnlineState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADC42C9C19E900780EF3 /* ListOnlineState.swift */; }; + A594ADC72C9C2ACC00780EF3 /* ProvideChannelDetailTypeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADC62C9C2ACC00780EF3 /* ProvideChannelDetailTypeUseCase.swift */; }; + A594ADC92C9C2C6A00780EF3 /* ProvideGroupDetailTypeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADC82C9C2C6A00780EF3 /* ProvideGroupDetailTypeUseCase.swift */; }; + A594ADCC2C9C3FF400780EF3 /* ThermostatSlavesVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADCB2C9C3FF400780EF3 /* ThermostatSlavesVM.swift */; }; + A594ADCE2C9C401700780EF3 /* ThermostatSlavesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADCD2C9C401700780EF3 /* ThermostatSlavesVC.swift */; }; + A594ADD02C9C402B00780EF3 /* ThermostatSlavesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADCF2C9C402B00780EF3 /* ThermostatSlavesView.swift */; }; + A594ADD22C9C439F00780EF3 /* ThermstatSlavesViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADD12C9C439F00780EF3 /* ThermstatSlavesViewState.swift */; }; + A594ADD42C9C451400780EF3 /* ThermostatSlavesFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADD32C9C451400780EF3 /* ThermostatSlavesFeature.swift */; }; + A594ADD62C9D43DE00780EF3 /* ReadChannelWithChildrenTreeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADD52C9D43DE00780EF3 /* ReadChannelWithChildrenTreeUseCase.swift */; }; + A594ADD82C9D504B00780EF3 /* ReadChannelWithChildrenTreeUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADD72C9D504B00780EF3 /* ReadChannelWithChildrenTreeUseCaseTests.swift */; }; + A594ADDA2C9D5E0B00780EF3 /* ChannelWithChildrenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A594ADD92C9D5E0B00780EF3 /* ChannelWithChildrenTests.swift */; }; A59683882ABE1E5A0005E73C /* ReadChannelWithChildrenUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59683872ABE1E5A0005E73C /* ReadChannelWithChildrenUseCaseTests.swift */; }; A596838A2ABE1FD10005E73C /* SAChannel+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59683892ABE1FD10005E73C /* SAChannel+Mock.swift */; }; A596838D2ABE26590005E73C /* DeleteRemovableChannelRelationUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A596838C2ABE26590005E73C /* DeleteRemovableChannelRelationUseCaseTests.swift */; }; @@ -895,6 +910,9 @@ A5D837E12AF132F6002A420D /* LoadChannelMeasurementsDateRangeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D837E02AF132F6002A420D /* LoadChannelMeasurementsDateRangeUseCase.swift */; }; A5D837E32AF3A728002A420D /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D837E22AF3A728002A420D /* LinkedList.swift */; }; A5DA31072AC16B21008179DB /* InsertChannelRelationForProfileUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DA31062AC16B21008179DB /* InsertChannelRelationForProfileUseCaseTests.swift */; }; + A5E039E52CA16C020018CEB7 /* HeatOrColdSourceSwitchIconNameProducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E039E42CA16C020018CEB7 /* HeatOrColdSourceSwitchIconNameProducer.swift */; }; + A5E039E72CA18E350018CEB7 /* Bool+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E039E62CA18E350018CEB7 /* Bool+Ext.swift */; }; + A5E039E92CA1F85C0018CEB7 /* LazyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E039E82CA1F85C0018CEB7 /* LazyList.swift */; }; A5E34CE42B848C6100DE511F /* NotificationViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E34CE32B848C6100DE511F /* NotificationViewCell.swift */; }; A5E40B4C2B84EA9300DB6ABE /* ApplicationEventsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E40B4B2B84EA9300DB6ABE /* ApplicationEventsManager.swift */; }; A5E40B4F2B85EB2100DB6ABE /* DownloadGeneralPurposeMeterLogUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E40B4E2B85EB2100DB6ABE /* DownloadGeneralPurposeMeterLogUseCaseTests.swift */; }; @@ -1007,7 +1025,7 @@ A5F29BE02A275F7600ED700A /* SwapGroupPositionsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BDF2A275F7600ED700A /* SwapGroupPositionsUseCase.swift */; }; A5F29BE22A27628300ED700A /* SwapScenePositionsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BE12A27628300ED700A /* SwapScenePositionsUseCase.swift */; }; A5F29BE62A27739000ED700A /* MoveableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BE52A27739000ED700A /* MoveableCell.swift */; }; - A5F29BEB2A27787600ED700A /* ProvideDetailTypeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BEA2A27787600ED700A /* ProvideDetailTypeUseCase.swift */; }; + A5F29BEB2A27787600ED700A /* BaseDetailTypeProviderUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BEA2A27787600ED700A /* BaseDetailTypeProviderUseCase.swift */; }; A5F29BEF2A287C6800ED700A /* SAChannelBase+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BEE2A287C6800ED700A /* SAChannelBase+Ext.swift */; }; A5F29BF12A28AE0B00ED700A /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BF02A28AE0B00ED700A /* NotificationView.swift */; }; A5F29BF32A2A197F00ED700A /* GroupCaptionEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F29BF22A2A197F00ED700A /* GroupCaptionEditor.swift */; }; @@ -1870,6 +1888,8 @@ A5477DCA2AA5EC4000220B4A /* CreateChannelWithChildrenUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateChannelWithChildrenUseCase.swift; sourceTree = ""; }; A5477DCC2AA5F7A900220B4A /* IssueIconType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueIconType.swift; sourceTree = ""; }; A5477DCE2AA616FD00220B4A /* CellButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellButton.swift; sourceTree = ""; }; + A547821E2CA2B4BB00CD3010 /* UiSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UiSettings.swift; sourceTree = ""; }; + A54782202CA2C12E00CD3010 /* View+ScaleFactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ScaleFactor.swift"; sourceTree = ""; }; A54A06492AF3DF6B00C03DBC /* SessionResponseProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseProvider.swift; sourceTree = ""; }; A54A064B2AF3E55700C03DBC /* SuplaSchedulers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuplaSchedulers.swift; sourceTree = ""; }; A54A064E2AF3ED6200C03DBC /* RequestHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestHelperTests.swift; sourceTree = ""; }; @@ -2095,6 +2115,19 @@ A58B2CB32AC195FA00764388 /* CreateTemperaturesListUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateTemperaturesListUseCaseTests.swift; sourceTree = ""; }; A58B2CB52AC197E300764388 /* ChannelBaseUseCasesMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelBaseUseCasesMocks.swift; sourceTree = ""; }; A58D98C72AC1A3AD00DCB526 /* SetChannelConfigUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetChannelConfigUseCaseTests.swift; sourceTree = ""; }; + A594ADBF2C9C07C600780EF3 /* ThermostatIndicatorIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermostatIndicatorIcon.swift; sourceTree = ""; }; + A594ADC22C9C0A9500780EF3 /* ThermostatIndicatorIconTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermostatIndicatorIconTests.swift; sourceTree = ""; }; + A594ADC42C9C19E900780EF3 /* ListOnlineState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOnlineState.swift; sourceTree = ""; }; + A594ADC62C9C2ACC00780EF3 /* ProvideChannelDetailTypeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvideChannelDetailTypeUseCase.swift; sourceTree = ""; }; + A594ADC82C9C2C6A00780EF3 /* ProvideGroupDetailTypeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvideGroupDetailTypeUseCase.swift; sourceTree = ""; }; + A594ADCB2C9C3FF400780EF3 /* ThermostatSlavesVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermostatSlavesVM.swift; sourceTree = ""; }; + A594ADCD2C9C401700780EF3 /* ThermostatSlavesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermostatSlavesVC.swift; sourceTree = ""; }; + A594ADCF2C9C402B00780EF3 /* ThermostatSlavesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermostatSlavesView.swift; sourceTree = ""; }; + A594ADD12C9C439F00780EF3 /* ThermstatSlavesViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermstatSlavesViewState.swift; sourceTree = ""; }; + A594ADD32C9C451400780EF3 /* ThermostatSlavesFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermostatSlavesFeature.swift; sourceTree = ""; }; + A594ADD52C9D43DE00780EF3 /* ReadChannelWithChildrenTreeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadChannelWithChildrenTreeUseCase.swift; sourceTree = ""; }; + A594ADD72C9D504B00780EF3 /* ReadChannelWithChildrenTreeUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadChannelWithChildrenTreeUseCaseTests.swift; sourceTree = ""; }; + A594ADD92C9D5E0B00780EF3 /* ChannelWithChildrenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelWithChildrenTests.swift; sourceTree = ""; }; A59683872ABE1E5A0005E73C /* ReadChannelWithChildrenUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadChannelWithChildrenUseCaseTests.swift; sourceTree = ""; }; A59683892ABE1FD10005E73C /* SAChannel+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SAChannel+Mock.swift"; sourceTree = ""; }; A596838C2ABE26590005E73C /* DeleteRemovableChannelRelationUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteRemovableChannelRelationUseCaseTests.swift; sourceTree = ""; }; @@ -2277,6 +2310,9 @@ A5D837E02AF132F6002A420D /* LoadChannelMeasurementsDateRangeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadChannelMeasurementsDateRangeUseCase.swift; sourceTree = ""; }; A5D837E22AF3A728002A420D /* LinkedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkedList.swift; sourceTree = ""; }; A5DA31062AC16B21008179DB /* InsertChannelRelationForProfileUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertChannelRelationForProfileUseCaseTests.swift; sourceTree = ""; }; + A5E039E42CA16C020018CEB7 /* HeatOrColdSourceSwitchIconNameProducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatOrColdSourceSwitchIconNameProducer.swift; sourceTree = ""; }; + A5E039E62CA18E350018CEB7 /* Bool+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bool+Ext.swift"; sourceTree = ""; }; + A5E039E82CA1F85C0018CEB7 /* LazyList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyList.swift; sourceTree = ""; }; A5E34CE32B848C6100DE511F /* NotificationViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewCell.swift; sourceTree = ""; }; A5E40B4B2B84EA9300DB6ABE /* ApplicationEventsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationEventsManager.swift; sourceTree = ""; }; A5E40B4E2B85EB2100DB6ABE /* DownloadGeneralPurposeMeterLogUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadGeneralPurposeMeterLogUseCaseTests.swift; sourceTree = ""; }; @@ -2389,7 +2425,7 @@ A5F29BDF2A275F7600ED700A /* SwapGroupPositionsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwapGroupPositionsUseCase.swift; sourceTree = ""; }; A5F29BE12A27628300ED700A /* SwapScenePositionsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwapScenePositionsUseCase.swift; sourceTree = ""; }; A5F29BE52A27739000ED700A /* MoveableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveableCell.swift; sourceTree = ""; }; - A5F29BEA2A27787600ED700A /* ProvideDetailTypeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvideDetailTypeUseCase.swift; sourceTree = ""; }; + A5F29BEA2A27787600ED700A /* BaseDetailTypeProviderUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDetailTypeProviderUseCase.swift; sourceTree = ""; }; A5F29BEE2A287C6800ED700A /* SAChannelBase+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SAChannelBase+Ext.swift"; sourceTree = ""; }; A5F29BF02A28AE0B00ED700A /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; A5F29BF22A2A197F00ED700A /* GroupCaptionEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCaptionEditor.swift; sourceTree = ""; }; @@ -3473,6 +3509,7 @@ A51BE8E92AA70D9600718F2F /* ThermostatDetail */ = { isa = PBXGroup; children = ( + A594ADCA2C9C3FD100780EF3 /* SlavesDetail */, A52BFEDA2B0F8EFC00A2F64C /* TimerDetail */, A56091732AE6858000AFE14F /* HistoryDetail */, A51BE8F12AA7185600718F2F /* ScheduleDetail */, @@ -3705,6 +3742,7 @@ A56233FE2ABAC488001CB948 /* ThermostatIconNameProducer.swift */, A55501E52B7F952E00FD3296 /* GeneralPurposeMeasurementIconNameProducer.swift */, A55501E92B7FC38500FD3296 /* GeneralPurposeMeterIconNameProducer.swift */, + A5E039E42CA16C020018CEB7 /* HeatOrColdSourceSwitchIconNameProducer.swift */, ); path = IconNameProducers; sourceTree = ""; @@ -4102,6 +4140,7 @@ A57C4AB42AAF458F00D9C695 /* SuplaScheduleProgram.swift */, A5477DC02AA5B4EC00220B4A /* SuplaThermostatFlag.swift */, A50B5CFC2BEA147100918D18 /* SuplaHeatpolThermostatFlag.swift */, + A594ADBF2C9C07C600780EF3 /* ThermostatIndicatorIcon.swift */, ); path = Thermostat; sourceTree = ""; @@ -4564,6 +4603,26 @@ path = Thermostat; sourceTree = ""; }; + A594ADC12C9C0A7F00780EF3 /* Thermostat */ = { + isa = PBXGroup; + children = ( + A594ADC22C9C0A9500780EF3 /* ThermostatIndicatorIconTests.swift */, + ); + path = Thermostat; + sourceTree = ""; + }; + A594ADCA2C9C3FD100780EF3 /* SlavesDetail */ = { + isa = PBXGroup; + children = ( + A594ADCB2C9C3FF400780EF3 /* ThermostatSlavesVM.swift */, + A594ADCD2C9C401700780EF3 /* ThermostatSlavesVC.swift */, + A594ADCF2C9C402B00780EF3 /* ThermostatSlavesView.swift */, + A594ADD12C9C439F00780EF3 /* ThermstatSlavesViewState.swift */, + A594ADD32C9C451400780EF3 /* ThermostatSlavesFeature.swift */, + ); + path = SlavesDetail; + sourceTree = ""; + }; A596838B2ABE26430005E73C /* ChannelRelation */ = { isa = PBXGroup; children = ( @@ -4788,6 +4847,7 @@ A5E40B5A2B8606F400DB6ABE /* GetChannelValueStringUseCaseTests.swift */, A5E40B702B8699CC00DB6ABE /* LoadChannelConfigUseCaseTests.swift */, A50B5D252BEE59B700918D18 /* DeleteChannelMeasurementsUseCaseTests.swift */, + A594ADD72C9D504B00780EF3 /* ReadChannelWithChildrenTreeUseCaseTests.swift */, ); path = Channel; sourceTree = ""; @@ -4860,11 +4920,15 @@ A5A15FF52C299C390049AA73 /* SwiftUiComponents */ = { isa = PBXGroup; children = ( + A54782202CA2C12E00CD3010 /* View+ScaleFactor.swift */, + A547821E2CA2B4BB00CD3010 /* UiSettings.swift */, + A5A15FF62C299C8A0049AA73 /* BackgroundStack.swift */, A5A1601B2C2BEE7F0049AA73 /* Button */, A5A1600A2C2AB4520049AA73 /* Architecture */, A5A160022C2AAEDE0049AA73 /* Text.swift */, A5A160192C2BEC6B0049AA73 /* FilledButton.swift */, A5F5C3F92C36963B0058E255 /* PinTextField.swift */, + A5E039E82CA1F85C0018CEB7 /* LazyList.swift */, ); path = SwiftUiComponents; sourceTree = ""; @@ -4900,7 +4964,6 @@ isa = PBXGroup; children = ( A5A15FF32C2994BF0049AA73 /* BorderedButton.swift */, - A5A15FF62C299C8A0049AA73 /* BackgroundStack.swift */, A5A1601C2C2BEE8D0049AA73 /* TextButton.swift */, ); path = Button; @@ -4936,6 +4999,7 @@ A5A23C2E2ABDAF8E00233542 /* ValuesFormatterTests.swift */, A54A06722AF8D31D00C03DBC /* HideableValueTests.swift */, A54A06742AF8D61000C03DBC /* LinkedListTests.swift */, + A594ADD92C9D5E0B00780EF3 /* ChannelWithChildrenTests.swift */, ); path = Model; sourceTree = ""; @@ -4943,6 +5007,7 @@ A5A23C2B2ABD96C600233542 /* SuplaClient */ = { isa = PBXGroup; children = ( + A594ADC12C9C0A7F00780EF3 /* Thermostat */, A5A23C2C2ABD96DB00233542 /* SuplaChannelConfigTests.swift */, A57C883B2ACB564B000B0F10 /* SuplaWeeklyScheduleProgramTests.swift */, A52BFEBE2B077CCB00A2F64C /* SuplaDeviceConfigTests.swift */, @@ -5555,6 +5620,7 @@ A58A9BFD2AA0A4BD00D28848 /* UIView+Ext.swift */, A58472002C2D7F3200713D36 /* UIViewController+SuplaNavBar.swift */, A50B5D232BEE4CE600918D18 /* UIViewController+Toast.swift */, + A5E039E62CA18E350018CEB7 /* Bool+Ext.swift */, ); path = Extensions; sourceTree = ""; @@ -5599,6 +5665,7 @@ A5F29B992A20C26900ED700A /* BaseTableViewController.swift */, A5F29BA02A20D6DD00ED700A /* ChannelBaseTableViewController.swift */, A5F29BE52A27739000ED700A /* MoveableCell.swift */, + A594ADC42C9C19E900780EF3 /* ListOnlineState.swift */, ); path = TableView; sourceTree = ""; @@ -5642,6 +5709,7 @@ A5B3CBDE2B62504D00F95AC3 /* GetChannelValueUseCase.swift */, A5B3CBF72B6260DD00F95AC3 /* GetChannelValueStringUseCase.swift */, A50B5D212BEE167300918D18 /* DeleteChannelMeasurementsUseCase.swift */, + A594ADD52C9D43DE00780EF3 /* ReadChannelWithChildrenTreeUseCase.swift */, ); path = Channel; sourceTree = ""; @@ -5717,7 +5785,9 @@ A5F29BE92A27785F00ED700A /* Detail */ = { isa = PBXGroup; children = ( - A5F29BEA2A27787600ED700A /* ProvideDetailTypeUseCase.swift */, + A5F29BEA2A27787600ED700A /* BaseDetailTypeProviderUseCase.swift */, + A594ADC62C9C2ACC00780EF3 /* ProvideChannelDetailTypeUseCase.swift */, + A594ADC82C9C2C6A00780EF3 /* ProvideGroupDetailTypeUseCase.swift */, ); path = Detail; sourceTree = ""; @@ -6174,6 +6244,7 @@ A5F29BC02A24DDB100ED700A /* UpdateChannelGroupRelationUseCase.swift in Sources */, A5F29B802A1E489300ED700A /* ProfileRepository.swift in Sources */, 010714782656C605009C119F /* SAZWaveWakeupSettingsReport.m in Sources */, + A594ADD02C9C402B00780EF3 /* ThermostatSlavesView.swift in Sources */, 40C7BA5420C008DB00ACEE42 /* SAChannelGroupRelation+CoreDataClass.m in Sources */, A5F29BB62A24CAAB00ED700A /* UpdateChannelValueUseCase.swift in Sources */, A50E5D752BFF543D00303BAE /* CurtainVC.swift in Sources */, @@ -6199,6 +6270,7 @@ 01D6A102249BEC8C006B3757 /* SAVLCalibrationTool.m in Sources */, A5F29BDE2A2737DA00ED700A /* SwapChannelPositionsUseCase.swift in Sources */, 404749BE20BC608B005DA345 /* _SALocation+CoreDataClass.m in Sources */, + A547821F2CA2B4D400CD3010 /* UiSettings.swift in Sources */, A57668F32AEAA2600025509D /* DownloadTempHumidityLogUseCase.swift in Sources */, A5B3A4B32BB552590001D006 /* BaseWindowView.swift in Sources */, A5B3CBF12B625DBA00F95AC3 /* PressureValueProvider.swift in Sources */, @@ -6227,6 +6299,7 @@ A5F29BE62A27739000ED700A /* MoveableCell.swift in Sources */, A5AE7A8F2A3AE0290097FA8B /* TitleArrowButtonCell.swift in Sources */, A5CE73292B4607AE003F882C /* EspConfigResult.swift in Sources */, + A594ADC52C9C19E900780EF3 /* ListOnlineState.swift in Sources */, A50B5D382BFB6D1400918D18 /* ProjectorScreenState.swift in Sources */, A55A8D752BA84D5400C540D4 /* RollerShutterVM.swift in Sources */, A530EE1A2A56FBBC00F8DAEE /* PowerSwitchIconNameProducer.swift in Sources */, @@ -6286,6 +6359,7 @@ A56233FB2AB88722001CB948 /* DelayedWeeklyScheduleConfigSubject.swift in Sources */, A530EE432A58AA6E00F8DAEE /* DeviceStateHelperVCI.swift in Sources */, A55501F52B83842000FD3296 /* NotificationsLogVC.swift in Sources */, + A5E039E92CA1F85C0018CEB7 /* LazyList.swift in Sources */, A55A8DAC2BAC60A200C540D4 /* SAAuthorizationDialogVM.swift in Sources */, A5E9CE372C3430DB00509702 /* Completable+Supla.swift in Sources */, 0148933425AA12DB00B9974E /* SADiwCalibrationTool.m in Sources */, @@ -6298,11 +6372,13 @@ A51BE9162AAB022400718F2F /* DayOfWeek.swift in Sources */, 01C1719122C7F3A2005983E1 /* SAElectricityMeasurementItem+CoreDataClass.m in Sources */, A52ACD4929CB940B0092729F /* ActionParameters.swift in Sources */, + A594ADC02C9C07C600780EF3 /* ThermostatIndicatorIcon.swift in Sources */, A503ABBA2B6BB0BF008CDA1F /* GpmValueFormatter.swift in Sources */, 40C7BA7720C047CF00ACEE42 /* SAChannel+CoreDataProperties.m in Sources */, A530EE302A57E2B900F8DAEE /* DigiglassVerticalIconNameProducer.swift in Sources */, A5A160072C2AB3ED0049AA73 /* SuplaCore+BaseViewModel.swift in Sources */, 01F8856E22E5E79100D18373 /* SAImpulseCounterMeasurementItem+CoreDataProperties.m in Sources */, + A5E039E52CA16C020018CEB7 /* HeatOrColdSourceSwitchIconNameProducer.swift in Sources */, A5074BD32BCE98760081B6B1 /* SlatTiltSlider.swift in Sources */, A52BFEC62B07904E00A2F64C /* SuplaButtonVolumeField.swift in Sources */, A5D837DF2AF12CA5002A420D /* LoadChannelMeasurementsUseCase.swift in Sources */, @@ -6310,8 +6386,9 @@ A58A9C012AA0AD0E00D28848 /* ThermostatCell.swift in Sources */, 0126B84125BDFF2F0027D88A /* SAZWaveConfigurationWizardVC.m in Sources */, A51BE8E82AA705AD00718F2F /* StandardDetailVC.swift in Sources */, - A5F29BEB2A27787600ED700A /* ProvideDetailTypeUseCase.swift in Sources */, + A5F29BEB2A27787600ED700A /* BaseDetailTypeProviderUseCase.swift in Sources */, 01C1718F22C7F3A2005983E1 /* SAIncrementalMeasurementItem+CoreDataClass.m in Sources */, + A594ADC92C9C2C6A00780EF3 /* ProvideGroupDetailTypeUseCase.swift in Sources */, A503ABAD2B67AC44008CDA1F /* LoadChannelConfigUseCase.swift in Sources */, A56233E52AB1AB69001CB948 /* UIIconButton.swift in Sources */, A56233DA2AB061EB001CB948 /* SACustomDialogVC.swift in Sources */, @@ -6341,6 +6418,7 @@ A541493C2B63E0D300B44BD6 /* LineChartData.swift in Sources */, A51BE9072AA8532600718F2F /* ThermostatControlView.swift in Sources */, 01A4D4F4269210E300F3DCD1 /* SAFormatter.m in Sources */, + A594ADCE2C9C401700780EF3 /* ThermostatSlavesVC.swift in Sources */, 0194D80A264DC8580094A857 /* SAZWaveNodeIdResult.m in Sources */, A553862829E004E400B5CF3F /* GlobalSettings.swift in Sources */, 01433DF8257F8FE20093C5DB /* SAWifi.m in Sources */, @@ -6467,8 +6545,10 @@ 013D1C5D22CE47D5000C0784 /* SAChartHelper.m in Sources */, A5F29B832A1F8D9B00ED700A /* SALocation+Ext.swift in Sources */, A5A15FEA2C2987450049AA73 /* UINavigationController+Ext.swift in Sources */, + A54782212CA2C13400CD3010 /* View+ScaleFactor.swift in Sources */, A566398B2ABAD52300BA51D7 /* LoadingTimeoutManager.swift in Sources */, 015A98412655958500B6E6C6 /* NSDictionary+SUPLA.m in Sources */, + A594ADD62C9D43DE00780EF3 /* ReadChannelWithChildrenTreeUseCase.swift in Sources */, 019F4CCD23577C3700286139 /* SAImpulseCounterChartHelper.m in Sources */, A5F29B762A1E33D500ED700A /* FetchedResultsControllerEntityObserver.swift in Sources */, A530EE452A58AAD800F8DAEE /* DeviceStateHelperVMI.swift in Sources */, @@ -6584,6 +6664,7 @@ A51BE90B2AAAF29D00718F2F /* SetpointType.swift in Sources */, A56233E12AB0F5B1001CB948 /* EditQuartersDialogVM.swift in Sources */, A5074BB82BCE58CA0081B6B1 /* BaseWindowVM.swift in Sources */, + A594ADD22C9C439F00780EF3 /* ThermstatSlavesViewState.swift in Sources */, A5AD701F2C00B45E00A36318 /* WindowControlButton.swift in Sources */, A50E5D7C2BFFD67D00303BAE /* ChannelBaseActionUseCase.swift in Sources */, A5D837D22AF1061C002A420D /* ThermometerHistoryDetailVM.swift in Sources */, @@ -6646,6 +6727,7 @@ A5D7128E2C4107AF00A8EF52 /* AboutFeature.swift in Sources */, A50B5D3F2BFC8BEA00918D18 /* ProjectorScreenColors.swift in Sources */, A5074BA82BC923AE0081B6B1 /* BrandingConfiguration.swift in Sources */, + A594ADD42C9C451400780EF3 /* ThermostatSlavesFeature.swift in Sources */, A51BE90D2AAAF82000718F2F /* GetChannelConfigUseCase.swift in Sources */, A5F29B5B2A1E15D600ED700A /* MainVC.swift in Sources */, 011021D325CC4DE700621D41 /* SAChannelBasicCfg.m in Sources */, @@ -6795,6 +6877,7 @@ A50B5D2B2BF49F3000918D18 /* TerraceAwningVC.swift in Sources */, A5EC52462C0F3DC10022F055 /* ReadGroupTiltingDetailsUseCase.swift in Sources */, A55A8D772BA84DCE00C540D4 /* RollerShutterVC.swift in Sources */, + A594ADC72C9C2ACC00780EF3 /* ProvideChannelDetailTypeUseCase.swift in Sources */, A530EE2C2A57E15000F8DAEE /* HeatpolHomeplusIconNameProducer.swift in Sources */, A54149302B63059200B44BD6 /* GpmHistoryDetailVC.swift in Sources */, AECB1BEB2711CB67001A9714 /* AuthInfo.swift in Sources */, @@ -6844,6 +6927,7 @@ A5B3CBE92B625CB300F95AC3 /* DepthValueProvider.swift in Sources */, A57C4AB32AAF374900D9C695 /* EditProgramDialogVC.swift in Sources */, A5074BC92BCE682C0081B6B1 /* BaseWallWindowView.swift in Sources */, + A594ADCC2C9C3FF400780EF3 /* ThermostatSlavesVM.swift in Sources */, 01CEABFA25B9920A009C3BFD /* SADigiglassDetailView.m in Sources */, 013F7EC123EB33E30061A497 /* SAChannelStateExtendedValue.m in Sources */, A5AD70292C04691100A36318 /* MoveTimeView.swift in Sources */, @@ -6858,6 +6942,7 @@ A5A160092C2AB42F0049AA73 /* SuplaCore+BaseViewController.swift in Sources */, A57668DF2AE99CCD0025509D /* LoadChannelWithChildrenMeasurementsUseCase.swift in Sources */, A56233F32AB3A386001CB948 /* CreateTemperaturesListUseCase.swift in Sources */, + A5E039E72CA18E350018CEB7 /* Bool+Ext.swift in Sources */, A50B5CFB2BEA144A00918D18 /* HeatpolThermostatValue.swift in Sources */, A5A14A312B60F76A004B1598 /* IconValueCell.swift in Sources */, A5AE7A852A3AC7020097FA8B /* BaseSettingsCell.swift in Sources */, @@ -6906,6 +6991,7 @@ A54A065D2AF4E58F00C03DBC /* CStructsConstructor.m in Sources */, A573B0AE2A6022C1001E19D0 /* GetDefaultIconNameUseCaseMock.swift in Sources */, A50B5D332BF54FF300918D18 /* TerraceAwningVMTests.swift in Sources */, + A594ADD82C9D504B00780EF3 /* ReadChannelWithChildrenTreeUseCaseTests.swift in Sources */, A59AB8AE2A3071A300D91F1F /* GroupListVMTests.swift in Sources */, A5E40B5D2B86075500DB6ABE /* ValueProviderMocks.swift in Sources */, A54A06752AF8D61000C03DBC /* LinkedListTests.swift in Sources */, @@ -6954,6 +7040,7 @@ A5EC52442C0F33990022F055 /* RequestChannelConfigUseCaseTests.swift in Sources */, A57638C629E5D4C9003E15A3 /* XCTestCaseExtensions.swift in Sources */, A5D712722C3E6FD700A8EF52 /* LockUseCasesMocks.swift in Sources */, + A594ADDA2C9D5E0B00780EF3 /* ChannelWithChildrenTests.swift in Sources */, A59AB8EB2A30AA2600D91F1F /* CreateProfileScenesListUseCaseTests.swift in Sources */, A55A8DC32BB2F4E000C540D4 /* NotificationCenterWrapperMock.swift in Sources */, 016ABE18236DA795001BF5FB /* KeychainTests.m in Sources */, @@ -7044,6 +7131,7 @@ A55A8DD12BB4411400C540D4 /* SAAuthorizationDialogVMTests.swift in Sources */, A58D98C82AC1A3AD00DCB526 /* SetChannelConfigUseCaseTests.swift in Sources */, A5D712882C3EB24900A8EF52 /* PinSetupVMTests.swift in Sources */, + A594ADC32C9C0A9500780EF3 /* ThermostatIndicatorIconTests.swift in Sources */, A5E40B5B2B8606F400DB6ABE /* GetChannelValueStringUseCaseTests.swift in Sources */, A52BFEFA2B18A43000A2F64C /* DeleteProfileUseCaseTests.swift in Sources */, A59AB8A62A305D8700D91F1F /* LocationUseCasesMocks.swift in Sources */, diff --git a/SUPLA/ChannelCell.m b/SUPLA/ChannelCell.m index 2f54cd47..8d42cf49 100644 --- a/SUPLA/ChannelCell.m +++ b/SUPLA/ChannelCell.m @@ -317,6 +317,8 @@ -(void) updateCellView { case SUPLA_CHANNELFNC_OPENINGSENSOR_WINDOW: case SUPLA_CHANNELFNC_MAILSENSOR: case SUPLA_CHANNELFNC_THERMOMETER: + case SUPLA_CHANNELFNC_PUMPSWITCH: + case SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH: self.left_OnlineStatus.hidden = NO; self.right_OnlineStatus.hidden = NO; self.right_OnlineStatus.shapeType = stRing; diff --git a/SUPLA/Core/DI/DiContainer.swift b/SUPLA/Core/DI/DiContainer.swift index 1ac98ce1..5c424183 100644 --- a/SUPLA/Core/DI/DiContainer.swift +++ b/SUPLA/Core/DI/DiContainer.swift @@ -123,6 +123,7 @@ extension DiContainer { register(CreateProfileChannelsListUseCase.self, CreateProfileChannelsListUseCaseImpl()) register(ReadChannelByRemoteIdUseCase.self, ReadChannelByRemoteIdUseCaseImpl()) register(ReadChannelWithChildrenUseCase.self, ReadChannelWithChildrenUseCaseImpl()) + register(ReadChannelWithChildrenTreeUseCase.self, ReadChannelWithChildrenTreeUseCaseImpl()) register(CreateTemperaturesListUseCase.self, CreateTemperaturesListUseCaseImpl()) register(DownloadChannelMeasurementsUseCase.self, DownloadChannelMeasurementsUseCaseImpl()) register(DownloadTemperatureLogUseCase.self, @@ -176,7 +177,8 @@ extension DiContainer { register(DisconnectUseCase.self, DisconnectUseCaseImpl()) register(ReconnectUseCase.self, ReconnectUseCaseImpl()) // Usecases - Detail - register(ProvideDetailTypeUseCase.self, ProvideDetailTypeUseCaseImpl()) + register(ProvideChannelDetailTypeUseCase.self, ProvideChannelDetailTypeUseCaseImpl()) + register(ProvideGroupDetailTypeUseCase.self, ProvideGroupDetailTypeUseCaseImpl()) // Usecases - Group register(SwapGroupPositionsUseCase.self, SwapGroupPositionsUseCaseImpl()) register(CreateProfileGroupsListUseCase.self, CreateProfileGroupsListUseCaseImpl()) diff --git a/SUPLA/Core/Extensions/Bool+Ext.swift b/SUPLA/Core/Extensions/Bool+Ext.swift new file mode 100644 index 00000000..385a4715 --- /dev/null +++ b/SUPLA/Core/Extensions/Bool+Ext.swift @@ -0,0 +1,27 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extension Bool { + func ifTrue(_ valueProvider: () -> T) -> T? { + if (self) { + return valueProvider() + } + + return nil + } +} diff --git a/SUPLA/Core/Extensions/Float+Ext.swift b/SUPLA/Core/Extensions/Float+Ext.swift index acd84339..9520f3e7 100644 --- a/SUPLA/Core/Extensions/Float+Ext.swift +++ b/SUPLA/Core/Extensions/Float+Ext.swift @@ -35,6 +35,10 @@ extension Float { func toSuplaTemperature() -> Int16 { return Int16(self * 100) } + + func also(_ transformation: (Float) -> T) -> T { + return transformation(self) + } } extension CGFloat: ScopeFunctions { diff --git a/SUPLA/Core/SwiftUiComponents/Architecture/SuplaCore+BaseViewController.swift b/SUPLA/Core/SwiftUiComponents/Architecture/SuplaCore+BaseViewController.swift index 2bc8eef8..65330567 100644 --- a/SUPLA/Core/SwiftUiComponents/Architecture/SuplaCore+BaseViewController.swift +++ b/SUPLA/Core/SwiftUiComponents/Architecture/SuplaCore+BaseViewController.swift @@ -100,7 +100,7 @@ extension SuplaCore { NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), hostingController.view.rightAnchor.constraint(equalTo: view.rightAnchor), - hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 8), hostingController.view.leftAnchor.constraint(equalTo: view.leftAnchor) ]) } diff --git a/SUPLA/Core/SwiftUiComponents/Button/BackgroundStack.swift b/SUPLA/Core/SwiftUiComponents/BackgroundStack.swift similarity index 84% rename from SUPLA/Core/SwiftUiComponents/Button/BackgroundStack.swift rename to SUPLA/Core/SwiftUiComponents/BackgroundStack.swift index a94e848b..2eee6975 100644 --- a/SUPLA/Core/SwiftUiComponents/Button/BackgroundStack.swift +++ b/SUPLA/Core/SwiftUiComponents/BackgroundStack.swift @@ -19,16 +19,21 @@ import SwiftUI struct BackgroundStack: View { + var alignment: Alignment = .center var color: Color = .Supla.background var content: () -> Content - init(color: Color = .Supla.background, @ViewBuilder content: @escaping () -> Content) { + init( + alignment: Alignment = .center, + color: Color = .Supla.background, + @ViewBuilder content: @escaping () -> Content + ) { self.color = color self.content = content } var body: some View { - ZStack { + ZStack(alignment: alignment) { if #available(iOS 14.0, *) { Color.Supla.background.ignoresSafeArea() } else { diff --git a/SUPLA/Core/SwiftUiComponents/LazyList.swift b/SUPLA/Core/SwiftUiComponents/LazyList.swift new file mode 100644 index 00000000..141facd3 --- /dev/null +++ b/SUPLA/Core/SwiftUiComponents/LazyList.swift @@ -0,0 +1,45 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +import SwiftUI + +struct LazyList: View { + var items: [Item] + var content: (Item) -> Content + + var body: some View { + ScrollView(.vertical) { + if #available(iOS 14.0, *) { + LazyVStack(spacing: 1) { + ForEach(items) { item in + content(item) + .listRowInsets(EdgeInsets()) + .listRowSeparatorInvisible() + } + } + } else { + SwiftUI.List(items) { item in + content(item) + .padding([.bottom], 1) + .listRowInsets(EdgeInsets()) + .listRowSeparatorInvisible() + }.listStyle(.plain) + } + } + } +} diff --git a/SUPLA/Core/SwiftUiComponents/Text.swift b/SUPLA/Core/SwiftUiComponents/Text.swift index a243035e..cae9f24b 100644 --- a/SUPLA/Core/SwiftUiComponents/Text.swift +++ b/SUPLA/Core/SwiftUiComponents/Text.swift @@ -161,4 +161,60 @@ struct Text { } } } + + struct CellValue: SuplaText { + @Environment(\.scaleFactor) var scaleFactor: CGFloat + + var text: String + + var body: some View { + if #available(iOS 15.0, *) { + SwiftUI.Text(text) + .font(Font.Supla.cellValue(scaleFactor, limit: .lower(1))) + .foregroundStyle(Color.Supla.onBackground) + } else { + SwiftUI.Text(text) + .font(Font.Supla.cellValue(scaleFactor, limit: .lower(1))) + .foregroundColor(Color.Supla.onBackground) + } + } + } + + struct CellCaption: SuplaText { + @Environment(\.scaleFactor) var scaleFactor: CGFloat + + var text: String + + var body: some View { + if #available(iOS 15.0, *) { + SwiftUI.Text(text) + .lineLimit(1) + .font(Font.Supla.cellCaption(scaleFactor, limit: .lower(1))) + .foregroundStyle(Color.Supla.onBackground) + } else { + SwiftUI.Text(text) + .lineLimit(1) + .font(Font.Supla.cellCaption(scaleFactor, limit: .lower(1))) + .foregroundColor(Color.Supla.onBackground) + } + } + } + + struct CellSubValue: SuplaText { + @Environment(\.scaleFactor) var scaleFactor: CGFloat + + var text: String + + var body: some View { + if #available(iOS 15.0, *) { + SwiftUI.Text(text) + .font(Font.Supla.cellSubValue(scaleFactor, limit: .lower(1))) + .foregroundStyle(Color.Supla.onBackground) + } else { + SwiftUI.Text(text) + .font(Font.Supla.cellSubValue(scaleFactor, limit: .lower(1))) + .foregroundColor(Color.Supla.onBackground) + } + } + } } diff --git a/SUPLA/Core/SwiftUiComponents/UiSettings.swift b/SUPLA/Core/SwiftUiComponents/UiSettings.swift new file mode 100644 index 00000000..c034a19e --- /dev/null +++ b/SUPLA/Core/SwiftUiComponents/UiSettings.swift @@ -0,0 +1,24 @@ +// +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +import SwiftUI + +final class UiSettings: ObservableObject { + @Published var scale: CGFloat = 1.0 +} diff --git a/SUPLA/Core/SwiftUiComponents/View+ScaleFactor.swift b/SUPLA/Core/SwiftUiComponents/View+ScaleFactor.swift new file mode 100644 index 00000000..17812f4e --- /dev/null +++ b/SUPLA/Core/SwiftUiComponents/View+ScaleFactor.swift @@ -0,0 +1,59 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +import SwiftUI + +extension EnvironmentValues { + @Entry var scaleFactor: CGFloat = 1.0 +} + +extension View { + func scale( + _ scaleFactor: CGFloat, + _ value: CGFloat, + limit: CellScalingLimit = .none + ) -> CGFloat { + SUPLA.scale(scaleFactor, value, limit: limit) + } +} + +extension CGFloat { + func scale( + _ value: CGFloat, + limit: CellScalingLimit = .none + ) -> CGFloat { + return SUPLA.scale(self, value, limit: limit) + } +} + +private func scale( + _ scaleFactor: CGFloat, + _ value: CGFloat, + limit: CellScalingLimit = .none +) -> CGFloat { + var scale = scaleFactor + switch (limit) { + case .lower(let val): + if (scaleFactor < val) { scale = val } + case .upper(let val): + if (scaleFactor > val) { scale = val } + default: break + } + + return value * scale +} diff --git a/SUPLA/Core/UI/Buttons/BaseControlButtonView.swift b/SUPLA/Core/UI/Buttons/BaseControlButtonView.swift index 96709a35..c31720d7 100644 --- a/SUPLA/Core/UI/Buttons/BaseControlButtonView.swift +++ b/SUPLA/Core/UI/Buttons/BaseControlButtonView.swift @@ -54,15 +54,15 @@ class BaseControlButtonView: UIView { didSet { switch (icon) { case .suplaIcon(let icon): - iconView.image = icon?.withRenderingMode(.alwaysTemplate) + iconView.image = .init(named: icon)?.withRenderingMode(.alwaysTemplate) iconView.tintColor = iconColor case .userIcon(let icon): iconView.image = icon default: - iconView.image = icon?.icon + iconView.image = icon?.uiImage } - iconView.isHidden = icon?.icon == nil + iconView.isHidden = icon == nil setupLayout() } } diff --git a/SUPLA/Core/UI/Details/StandardDetailVC.swift b/SUPLA/Core/UI/Details/StandardDetailVC.swift index b850694b..0c165646 100644 --- a/SUPLA/Core/UI/Details/StandardDetailVC.swift +++ b/SUPLA/Core/UI/Details/StandardDetailVC.swift @@ -75,6 +75,8 @@ class StandardDetailVC viewControllers.append(legacyDetail(type: .ic)) case .thermostatGeneral: viewControllers.append(thermostatGeneral()) + case .thermostatList: + viewControllers.append(thermostatList()) case .schedule: viewControllers.append(scheduleDetail()) case .thermostatHistory: @@ -159,6 +161,16 @@ class StandardDetailVC return vc } + private func thermostatList() -> UIViewController { + let vc = ThermostatSlavesFeature.ViewController.create(item: item) + vc.tabBarItem = UITabBarItem( + title: "List", + image: UIImage(named: "list"), + tag: DetailTabTag.List.rawValue + ) + return vc + } + private func scheduleDetail() -> ScheduleDetailVC { let vc = ScheduleDetailVC(item: item) vc.tabBarItem = UITabBarItem( @@ -315,4 +327,5 @@ fileprivate enum DetailTabTag: Int { case Schedule = 5 case ThermostatHistory = 6 case Window = 7 + case List = 8 } diff --git a/SUPLA/Core/UI/TableView/BaseTableViewModel.swift b/SUPLA/Core/UI/TableView/BaseTableViewModel.swift index 6301ea52..71ddacdd 100644 --- a/SUPLA/Core/UI/TableView/BaseTableViewModel.swift +++ b/SUPLA/Core/UI/TableView/BaseTableViewModel.swift @@ -45,8 +45,8 @@ class BaseTableViewModel: BaseViewModel { func onClicked(onItem item: Any) {} - func isAvailableInOffline(_ function: Int32, subValueType: Int32? = nil) -> Bool { - switch (function) { + func isAvailableInOffline(_ channel: SAChannelBase, children: [ChannelChild]? = nil) -> Bool { + switch (channel.func) { case SUPLA_CHANNELFNC_THERMOMETER, SUPLA_CHANNELFNC_HUMIDITYANDTEMPERATURE, SUPLA_CHANNELFNC_ELECTRICITY_METER, @@ -70,12 +70,16 @@ class BaseTableViewModel: BaseViewModel { case SUPLA_CHANNELFNC_LIGHTSWITCH, SUPLA_CHANNELFNC_POWERSWITCH, SUPLA_CHANNELFNC_STAIRCASETIMER: - switch subValueType { - case SUBV_TYPE_IC_MEASUREMENTS, - SUBV_TYPE_ELECTRICITY_MEASUREMENTS: + if (children?.first(where: { $0.relationType == .meter }) != nil) { return true - default: - return false + } else { + switch (Int32((channel as? SAChannel)?.value?.sub_value_type ?? 0)) { + case SUBV_TYPE_IC_MEASUREMENTS, + SUBV_TYPE_ELECTRICITY_MEASUREMENTS: + return true + default: + return false + } } default: return false diff --git a/SUPLA/Core/UI/TableView/Cells/BaseCell.swift b/SUPLA/Core/UI/TableView/Cells/BaseCell.swift index 6bfacb12..8057242d 100644 --- a/SUPLA/Core/UI/TableView/Cells/BaseCell.swift +++ b/SUPLA/Core/UI/TableView/Cells/BaseCell.swift @@ -105,6 +105,7 @@ class BaseCell: MGSwipeTableCell { label.isUserInteractionEnabled = true label.addGestureRecognizer(captionLongPressRecongizer) label.textColor = .onBackground + label.textAlignment = .center return label }() @@ -309,8 +310,9 @@ class BaseCell: MGSwipeTableCell { let bottomAnchor = contentView.bottomAnchor var constraints = [ - captionView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), captionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -scale(Dimens.ListItem.verticalPadding)), + captionView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: Dimens.distanceDefault), + captionView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -Dimens.distanceDefault), timerView.topAnchor.constraint(equalTo: topAnchor, constant: scale(10)), timerView.rightAnchor.constraint(equalTo: rightMarginAnchor, constant: -Dimens.ListItem.horizontalPadding), @@ -503,6 +505,26 @@ enum CellScalingLimit { } class CellStatusIndicatorView: UIView { + private lazy var topLayer: CAShapeLayer = { + let layer = CAShapeLayer() + layer.cornerRadius = Dimens.ListItem.statusIndicatorSize / 2 + layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + layer.borderWidth = 1 + layer.borderColor = UIColor.primary.cgColor + layer.backgroundColor = UIColor.primary.cgColor + return layer + }() + + private lazy var bottomLayer: CAShapeLayer = { + let layer = CAShapeLayer() + layer.cornerRadius = Dimens.ListItem.statusIndicatorSize / 2 + layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] + layer.borderWidth = 1 + layer.borderColor = UIColor.error.cgColor + layer.backgroundColor = UIColor.error.cgColor + return layer + }() + override init(frame: CGRect) { super.init(frame: frame) setupView() @@ -513,20 +535,40 @@ class CellStatusIndicatorView: UIView { setupView() } - func configure(filled: Bool, online: Bool) { - let color = online ? UIColor.primary : UIColor.error - if (filled) { - layer.borderColor = color.cgColor - backgroundColor = color - } else { - layer.borderColor = color.cgColor + override func layoutSubviews() { + super.layoutSubviews() + topLayer.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height / 2) + bottomLayer.frame = CGRect(x: 0, y: frame.height / 2, width: frame.width, height: frame.height / 2) + } + + func configure(filled: Bool, onlineState: ListOnlineState) { + let color = onlineState.online ? UIColor.primary : UIColor.error + + switch (onlineState) { + case .online, .offline, .unknown: + topLayer.isHidden = true + bottomLayer.isHidden = true + + if (filled) { + layer.borderColor = color.cgColor + backgroundColor = color + } else { + layer.borderColor = color.cgColor + backgroundColor = .clear + } + case .partiallyOnline: + layer.borderColor = UIColor.clear.cgColor backgroundColor = .clear + topLayer.isHidden = false + bottomLayer.isHidden = false } } func setInvisible() { layer.borderColor = UIColor.clear.cgColor backgroundColor = .clear + topLayer.isHidden = true + bottomLayer.isHidden = true } func constraints() -> [NSLayoutConstraint] { @@ -542,5 +584,8 @@ class CellStatusIndicatorView: UIView { layer.borderWidth = 1 setInvisible() + + layer.addSublayer(topLayer) + layer.addSublayer(bottomLayer) } } diff --git a/SUPLA/Core/UI/TableView/ListOnlineState.swift b/SUPLA/Core/UI/TableView/ListOnlineState.swift new file mode 100644 index 00000000..b9466b4a --- /dev/null +++ b/SUPLA/Core/UI/TableView/ListOnlineState.swift @@ -0,0 +1,47 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +enum ListOnlineState { + case online, partiallyOnline, offline, unknown + + var online: Bool { + self == .online || self == .partiallyOnline + } + + func mergeWith(_ other: ListOnlineState?) -> ListOnlineState { + if (self == other) { + self + } else if (self == .partiallyOnline || other == .partiallyOnline) { + .partiallyOnline + } else if (self == .online && other == .offline) { + .partiallyOnline + } else if (self == .offline && other == .online) { + .partiallyOnline + } else if (self == .online || other == .online) { + .online + } else { + .offline + } + } + + static func from(_ online: Bool) -> ListOnlineState { online ? .online : .offline } +} + +extension Bool { + var onlineState: ListOnlineState { ListOnlineState.from(self) } +} diff --git a/SUPLA/Core/UI/Views/ThermostatControlView.swift b/SUPLA/Core/UI/Views/ThermostatControlView.swift index ee5a122a..2e6f3106 100644 --- a/SUPLA/Core/UI/Views/ThermostatControlView.swift +++ b/SUPLA/Core/UI/Views/ThermostatControlView.swift @@ -104,7 +104,9 @@ final class ThermostatControlView: UIView { var operationalMode: ThermostatOperationalMode = .offline { didSet { - temperatureCircleShape.operationalMode = operationalMode + traitCollection.performAsCurrent { + temperatureCircleShape.operationalMode = operationalMode + } indicatorHeatingShape.isHidden = operationalMode != .heating indicatorCoolingShape.isHidden = operationalMode != .cooling currentPowerLabel.isHidden = operationalMode != .heating && operationalMode != .cooling || currentPower <= 1 @@ -260,7 +262,6 @@ final class ThermostatControlView: UIView { width: currentPowerLabel.intrinsicContentSize.width, height: currentPowerLabel.intrinsicContentSize.height ) - } override func draw(_ rect: CGRect) { diff --git a/SUPLA/Features/ChannelList/Cells/IconCell.swift b/SUPLA/Features/ChannelList/Cells/IconCell.swift index 15a35679..30b4fd00 100644 --- a/SUPLA/Features/ChannelList/Cells/IconCell.swift +++ b/SUPLA/Features/ChannelList/Cells/IconCell.swift @@ -75,10 +75,10 @@ final class IconCell: BaseCell { caption = getChannelBaseCaptionUseCase.invoke(channelBase: channel) - leftStatusIndicatorView.configure(filled: hasLeftButton(), online: channel.isOnline()) - rightStatusIndicatorView.configure(filled: hasRightButton(), online: channel.isOnline()) + leftStatusIndicatorView.configure(filled: hasLeftButton(), onlineState: channel.onlineState) + rightStatusIndicatorView.configure(filled: hasRightButton(), onlineState: channel.onlineState) - iconView.image = getChannelBaseIconUseCase.invoke(channel: channel) + iconView.image = getChannelBaseIconUseCase.invoke(channel: channel).uiImage issueIcon = nil } diff --git a/SUPLA/Features/ChannelList/Cells/IconValueCell.swift b/SUPLA/Features/ChannelList/Cells/IconValueCell.swift index fe654c45..c75d9929 100644 --- a/SUPLA/Features/ChannelList/Cells/IconValueCell.swift +++ b/SUPLA/Features/ChannelList/Cells/IconValueCell.swift @@ -92,10 +92,10 @@ final class IconValueCell: BaseCell { caption = getChannelBaseCaptionUseCase.invoke(channelBase: channel) - leftStatusIndicatorView.configure(filled: false, online: channel.isOnline()) - rightStatusIndicatorView.configure(filled: false, online: channel.isOnline()) + leftStatusIndicatorView.configure(filled: false, onlineState: channel.onlineState) + rightStatusIndicatorView.configure(filled: false, onlineState: channel.onlineState) - iconView.image = getChannelBaseIconUseCase.invoke(channel: channel) + iconView.image = getChannelBaseIconUseCase.invoke(channel: channel).uiImage valueView.text = getChannelValueStringUseCase.invoke(channel) issueIcon = nil diff --git a/SUPLA/Features/ChannelList/Cells/ThermostatCell.swift b/SUPLA/Features/ChannelList/Cells/ThermostatCell.swift index 10a8f98d..f6853b9f 100644 --- a/SUPLA/Features/ChannelList/Cells/ThermostatCell.swift +++ b/SUPLA/Features/ChannelList/Cells/ThermostatCell.swift @@ -79,13 +79,7 @@ final class ThermostatCell: BaseCell { override func online() -> Bool { data?.channel.isOnline() ?? false } override func issueMessage() -> String? { - if (data?.channel.value?.asThermostatValue().flags.contains(.thermometerError) == true) { - return Strings.ThermostatDetail.thermometerError - } else if (data?.channel.value?.asThermostatValue().flags.contains(.clockError) == true) { - return Strings.ThermostatDetail.clockError - } else { - return nil - } + data?.channel.value?.asThermostatValue().issueText } override func derivedClassControls() -> [UIView] { @@ -164,26 +158,22 @@ final class ThermostatCell: BaseCell { caption = getChannelBaseCaptionUseCase.invoke(channelBase: channel) - leftStatusIndicatorView.configure(filled: true, online: channel.isOnline()) - rightStatusIndicatorView.configure(filled: true, online: channel.isOnline()) + let onlineState = ListOnlineState.from(channel.isOnline()).mergeWith(data.children.onlineState) + leftStatusIndicatorView.configure(filled: true, onlineState: onlineState) + rightStatusIndicatorView.configure(filled: true, onlineState: onlineState) thermostatIconView.image = getChannelBaseIconUseCase.invoke( channel: channel, subfunction: thermostatValue?.subfunction - ) + ).uiImage indicatorView.image = .iconStandby issueIcon = nil if let thermostatValue = thermostatValue { - setpointTemperatureView.text = getSetpointTemperatureString(channel, thermostatValue) - indicatorView.image = getIndicatorIcon(channel, thermostatValue) - - if (channel.isOnline() && thermostatValue.flags.contains(.thermometerError)) { - issueIcon = .error - } else if (channel.isOnline() && thermostatValue.flags.contains(.clockError)) { - issueIcon = .warning - } + setpointTemperatureView.text = thermostatValue.setpointText + indicatorView.image = thermostatValue.indicatorIcon.mergeWith(data.children.indicatorIcon).resource + issueIcon = thermostatValue.issueIcon } if let mainThermometer = data.children.first(where: { $0.relationType == .mainThermometer })?.channel { @@ -196,34 +186,4 @@ final class ThermostatCell: BaseCell { override func timerEndDate() -> Date? { data?.channel.getTimerEndDate() } - - private func getSetpointTemperatureString(_ channel: SAChannel, _ thermostatValue: ThermostatValue) -> String { - if (!channel.isOnline()) { - return "" - } - switch (thermostatValue.mode) { - case .cool: return formatter.temperatureToString(thermostatValue.setpointTemperatureCool) - case .heat: return formatter.temperatureToString(thermostatValue.setpointTemperatureHeat) - case .off: return "Off" - case .auto: - let min = formatter.temperatureToString(thermostatValue.setpointTemperatureHeat) - let max = formatter.temperatureToString(thermostatValue.setpointTemperatureCool) - return "\(min) - \(max)" - default: return "" - } - } - - private func getIndicatorIcon(_ channel: SAChannel, _ thermostatValue: ThermostatValue) -> UIImage? { - if (thermostatValue.flags.contains(.forcedOffBySensor)) { - return .iconSensorAlert - } else if (channel.isOnline() && thermostatValue.flags.contains(.cooling)) { - return .iconCooling - } else if (channel.isOnline() && thermostatValue.flags.contains(.heating)) { - return .iconHeating - } else if (channel.isOnline() && thermostatValue.mode != .off) { - return .iconStandby - } else { - return nil - } - } } diff --git a/SUPLA/Features/ChannelList/ChannelListVM.swift b/SUPLA/Features/ChannelList/ChannelListVM.swift index e41e8429..312edf7b 100644 --- a/SUPLA/Features/ChannelList/ChannelListVM.swift +++ b/SUPLA/Features/ChannelList/ChannelListVM.swift @@ -21,9 +21,10 @@ import Foundation class ChannelListViewModel: BaseTableViewModel { @Singleton private var createProfileChannelsListUseCase @Singleton private var swapChannelPositionsUseCase - @Singleton private var provideDetailTypeUseCase + @Singleton private var provideDetailTypeUseCase @Singleton private var updateEventsManager @Singleton private var channelBaseActionUseCase + @Singleton private var readChannelWithChildrenUseCase override init() { super.init() @@ -53,47 +54,58 @@ class ChannelListViewModel: BaseTableViewModel CollapsedFlag { .channel } + + func onButtonClicked(buttonType: CellButtonType, data: Any?) { + if let channelWithChildren = data as? ChannelWithChildren { + channelBaseActionUseCase.invoke(channelWithChildren.channel, buttonType) + .asDriverWithoutError() + .drive() + .disposed(by: self) + } + } + + func onNoContentButtonClicked() { + send(event: .showAddWizard) + } + + private func handleClickedItem(_ channelWithChildren: ChannelWithChildren) { + let channel = channelWithChildren.channel + let subValueType = channel.value?.sub_value_type ?? 0 + if (!isAvailableInOffline(channel, children: channelWithChildren.children) && !channel.isOnline()) { return // do not open details for offline channels } guard - let detailType = provideDetailTypeUseCase.invoke(channelBase: item) + let detailType = provideDetailTypeUseCase.invoke(channelWithChildren: channelWithChildren) else { return } switch (detailType) { case let .legacy(type: legacyDetailType): - send(event: .navigateToDetail(legacy: legacyDetailType, channelBase: item)) + send(event: .navigateToDetail(legacy: legacyDetailType, channelBase: channel)) case let .switchDetail(pages): - send(event: .navigateToSwitchDetail(item: item.item(), pages: pages)) + send(event: .navigateToSwitchDetail(item: channel.item(), pages: pages)) case let .thermostatDetail(pages): - send(event: .navigateToThermostatDetail(item: item.item(), pages: pages)) + send(event: .navigateToThermostatDetail(item: channel.item(), pages: pages)) case let .thermometerDetail(pages): - send(event: .navigateToThermometerDetail(item: item.item(), pages: pages)) + send(event: .navigateToThermometerDetail(item: channel.item(), pages: pages)) case let .gpmDetail(pages): - send(event: .navigateToGpmDetail(item: item.item(), pages: pages)) + send(event: .navigateToGpmDetail(item: channel.item(), pages: pages)) case let .windowDetail(pages): - send(event: .navigateToRollerShutterDetail(item: item.item(), pages: pages)) + send(event: .navigateToRollerShutterDetail(item: channel.item(), pages: pages)) } } - - override func getCollapsedFlag() -> CollapsedFlag { .channel } - - func onButtonClicked(buttonType: CellButtonType, data: Any?) { - if let channelWithChildren = data as? ChannelWithChildren { - channelBaseActionUseCase.invoke(channelWithChildren.channel, buttonType) - .asDriverWithoutError() - .drive() - .disposed(by: self) - } - } - - func onNoContentButtonClicked() { - send(event: .showAddWizard) - } } enum ChannelListViewEvent: ViewEvent { diff --git a/SUPLA/Features/Details/DetailBase/History/BaseHistoryDetailVC.swift b/SUPLA/Features/Details/DetailBase/History/BaseHistoryDetailVC.swift index b36cb957..0fd2a44f 100644 --- a/SUPLA/Features/Details/DetailBase/History/BaseHistoryDetailVC.swift +++ b/SUPLA/Features/Details/DetailBase/History/BaseHistoryDetailVC.swift @@ -318,7 +318,7 @@ private class DataSetsRowView: HorizontalyScrollableView { private class DataSetItem: UIView { private var color: UIColor - private var icon: UIImage? + private var icon: IconResult? private var value: String var tapEvents: ControlEvent { buttonView.rx.tap } @@ -347,7 +347,7 @@ private class DataSetItem: UIView { let view = UIImageView() view.translatesAutoresizingMaskIntoConstraints = false view.contentMode = .scaleAspectFit - view.image = icon + view.image = icon?.uiImage return view }() @@ -367,7 +367,7 @@ private class DataSetItem: UIView { return button }() - init(icon: UIImage?, color: UIColor, value: String) { + init(icon: IconResult?, color: UIColor, value: String) { self.icon = icon self.color = color self.value = value diff --git a/SUPLA/Features/Details/SwitchDetail/DeviceStateHelper/DeviceStateHelperVCI.swift b/SUPLA/Features/Details/SwitchDetail/DeviceStateHelper/DeviceStateHelperVCI.swift index ee96acee..fa319c72 100644 --- a/SUPLA/Features/Details/SwitchDetail/DeviceStateHelper/DeviceStateHelperVCI.swift +++ b/SUPLA/Features/Details/SwitchDetail/DeviceStateHelper/DeviceStateHelperVCI.swift @@ -39,6 +39,6 @@ extension DeviceStateHelperVCI { view.label = Strings.SwitchDetail.stateLabel } - view.icon = getChannelBaseIconUseCase.invoke(iconData: state.iconData).icon + view.icon = getChannelBaseIconUseCase.invoke(iconData: state.iconData).uiImage } } diff --git a/SUPLA/Features/Details/SwitchDetail/DeviceStateView.swift b/SUPLA/Features/Details/SwitchDetail/DeviceStateView.swift index 3cc4b0d2..6fbfaa23 100644 --- a/SUPLA/Features/Details/SwitchDetail/DeviceStateView.swift +++ b/SUPLA/Features/Details/SwitchDetail/DeviceStateView.swift @@ -1,23 +1,22 @@ /* -Copyright (C) AC SOFTWARE SP. Z O.O. + Copyright (C) AC SOFTWARE SP. Z O.O. -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ -final class DeviceState { - +enum DeviceState { static func endDateText(_ timerEndDate: Date?) -> String { guard let date = timerEndDate else { return "" } @@ -26,18 +25,14 @@ final class DeviceState { return Strings.TimerDetail.stateLabelForTimerDays.arguments(dateString) } - static func currentStateIcon(_ mode: SuplaHvacMode?) -> UIImage? { - mode?.icon - } + static func currentStateIcon(_ mode: SuplaHvacMode?) -> String? { mode?.icon } - static func currentStateIconColor(_ mode: SuplaHvacMode?) -> UIColor { - return mode?.iconColor ?? .disabled - } + static func currentStateIconColor(_ mode: SuplaHvacMode?) -> UIColor { mode?.iconColor ?? .disabled } static func currentStateValue(_ mode: SuplaHvacMode?, heatSetpoint: Float?, coolSetpoint: Float?) -> String { @Singleton var formatter - return switch(mode) { + return switch (mode) { case .off: "OFF" case .heat: formatter.temperatureToString(heatSetpoint, withUnit: false) case .cool: formatter.temperatureToString(coolSetpoint, withUnit: false) @@ -47,7 +42,6 @@ final class DeviceState { } final class DeviceStateView: UIStackView { - var label: String? = nil { didSet { labelView.text = label?.uppercased() } } @@ -96,6 +90,7 @@ final class DeviceStateView: UIStackView { setupView() } + @available(*, unavailable) required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/ScheduleDetailVC.swift b/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/ScheduleDetailVC.swift index 602574d2..b1ce9b4d 100644 --- a/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/ScheduleDetailVC.swift +++ b/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/ScheduleDetailVC.swift @@ -191,7 +191,7 @@ fileprivate class ButtonsRowView: HorizontalyScrollableView UIImage? { + private func getProgramIcon(_ program: SuplaWeeklyScheduleProgram) -> String? { if (channelFunc == SUPLA_CHANNELFNC_HVAC_THERMOSTAT_HEAT_COOL) { return program.mode.icon } diff --git a/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/dialogs/EditQuartersDialogVC.swift b/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/dialogs/EditQuartersDialogVC.swift index be3503b6..fc9f4c80 100644 --- a/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/dialogs/EditQuartersDialogVC.swift +++ b/SUPLA/Features/Details/ThermostatDetail/ScheduleDetail/dialogs/EditQuartersDialogVC.swift @@ -249,7 +249,7 @@ fileprivate class ButtonsRowView: UIView { for program in programs { let buttonView = RoundedControlButtonView(height: Dimens.buttonSmallHeight) buttonView.backgroundColor = program.scheduleProgram.program.color() - buttonView.icon = program.icon != nil ? .suplaIcon(icon: program.icon) : nil + buttonView.icon = program.icon != nil ? .suplaIcon(name: program.icon!) : nil buttonView.text = program.text buttonView.textFont = .scheduleDetailButton buttonView.type = .neutral diff --git a/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesFeature.swift b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesFeature.swift new file mode 100644 index 00000000..af9e03f8 --- /dev/null +++ b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesFeature.swift @@ -0,0 +1,19 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +struct ThermostatSlavesFeature {} diff --git a/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesVC.swift b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesVC.swift new file mode 100644 index 00000000..e3d7dd5d --- /dev/null +++ b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesVC.swift @@ -0,0 +1,60 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +import SwiftUI + +extension ThermostatSlavesFeature { + class ViewController: SuplaCore.BaseViewController { + @Singleton private var coordinator + + private let item: ItemBundle + + init(viewModel: ViewModel, item: ItemBundle) { + self.item = item + super.init(viewModel: viewModel) + + contentView = ThermostatSlavesFeature.View( + viewState: state, + onInfoAction: { [weak self] in self?.onIssueIconTapped(issueMessage: $0) }, + onStatusAction: { + if let channel = $0 { + SAChannelStatePopup.globalInstance().show(channel) + } + } + ) + } + + override func viewDidLoad() { + super.viewDidLoad() + viewModel.loadData(item.remoteId) + } + + func onIssueIconTapped(issueMessage: String) { + let alert = UIAlertController(title: "SUPLA", message: issueMessage, preferredStyle: .alert) + let okButton = UIAlertAction(title: Strings.General.ok, style: .default) + + alert.title = NSLocalizedString("Warning", comment: "") + alert.addAction(okButton) + coordinator.present(alert, animated: true) + } + + static func create(item: ItemBundle) -> UIViewController { + ViewController(viewModel: ViewModel(), item: item) + } + } +} diff --git a/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesVM.swift b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesVM.swift new file mode 100644 index 00000000..d7312225 --- /dev/null +++ b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesVM.swift @@ -0,0 +1,124 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extension ThermostatSlavesFeature { + class ViewModel: SuplaCore.BaseViewModel { + @Singleton private var readChannelWithChildrenTreeUseCase: ReadChannelWithChildrenTreeUseCase + @Singleton private var globalSettings: GlobalSettings + + init() { + super.init(state: ViewState()) + } + + override func onViewDidLoad() { + state.scale = CGFloat(globalSettings.channelHeight.factor()) + } + + func loadData(_ remoteId: Int32) { + readChannelWithChildrenTreeUseCase.invoke(remoteId: remoteId) + .asDriverWithoutError() + .drive( + onNext: { [weak self] in self?.handle(channel: $0) } + ) + .disposed(by: disposeBag) + } + + private func handle(channel: ChannelWithChildren) { + state.master = channel.toThermostatData() + state.slaves = channel.allDescendantFlat + .filter { $0.relationType == .masterThermostat } + .map { $0.toThermostatData() } + } + } +} + +private extension ChannelChild { + func toThermostatData() -> ThermostatSlavesFeature.ThermostatData { + @Singleton var getChannelCaptionUseCase: GetChannelBaseCaptionUseCase + @Singleton var getChannelIconUseCase: GetChannelBaseIconUseCase + @Singleton var getChannelValueStringUseCase: GetChannelValueStringUseCase + @Singleton var valuesFormatter: ValuesFormatter + + let thermostatValue = channel.value?.asThermostatValue() + let mainThermometer = children.first(where: { $0.relationType == .mainThermometer }) + let pumpSwitchChild = children.first(where: { $0.relationType == .pumpSwitch }) + let sourceSwitchChild = children.first(where: { $0.relationType == .heatOrColdSourceSwitch }) + + let value = mainThermometer != nil ? getChannelValueStringUseCase.invoke(mainThermometer!.channel) : NO_VALUE_TEXT + + return ThermostatSlavesFeature.ThermostatData( + id: channel.remote_id, + onlineState: ListOnlineState.from(channel.isOnline()), + caption: getChannelCaptionUseCase.invoke(channelBase: channel), + icon: getChannelIcon(channel), + currentPower: thermostatValue?.state.power?.also { valuesFormatter.percentageToString($0/100) }, + value: value, + indicatorIcon: thermostatValue?.indicatorIcon ?? .off, + issueIconType: thermostatValue?.issueIcon, + issueMessage: thermostatValue?.issueText, + showChannelStateIcon: channel.flags & Int64(SUPLA_CHANNEL_FLAG_CHANNELSTATE) > 0, + subValue: thermostatValue?.setpointText, + pumpSwitchIcon: getChannelIcon(pumpSwitchChild?.channel), + sourceSwitchIcon: getChannelIcon(sourceSwitchChild?.channel), + channel: channel + ) + } + + +} + +private extension ChannelWithChildren { + func toThermostatData() -> ThermostatSlavesFeature.ThermostatData { + @Singleton var getChannelCaptionUseCase: GetChannelBaseCaptionUseCase + @Singleton var getChannelIconUseCase: GetChannelBaseIconUseCase + @Singleton var getChannelValueStringUseCase: GetChannelValueStringUseCase + @Singleton var valuesFormatter: ValuesFormatter + + let thermostatValue = channel.value?.asThermostatValue() + let mainThermometer = children.first(where: { $0.relationType == .mainThermometer }) + let pumpSwitchChild = children.first(where: { $0.relationType == .pumpSwitch }) + let sourceSwitchChild = children.first(where: { $0.relationType == .heatOrColdSourceSwitch }) + + let value = mainThermometer != nil ? getChannelValueStringUseCase.invoke(mainThermometer!.channel) : NO_VALUE_TEXT + + return ThermostatSlavesFeature.ThermostatData( + id: channel.remote_id, + onlineState: ListOnlineState.from(channel.isOnline()), + caption: getChannelCaptionUseCase.invoke(channelBase: channel), + icon: getChannelIcon(channel), + currentPower: thermostatValue?.state.power?.also { valuesFormatter.percentageToString($0/100) }, + value: value, + indicatorIcon: thermostatValue?.indicatorIcon ?? .off, + issueIconType: thermostatValue?.issueIcon, + issueMessage: thermostatValue?.issueText, + showChannelStateIcon: channel.flags & Int64(SUPLA_CHANNEL_FLAG_CHANNELSTATE) > 0, + subValue: thermostatValue?.setpointText, + pumpSwitchIcon: getChannelIcon(pumpSwitchChild?.channel), + sourceSwitchIcon: getChannelIcon(sourceSwitchChild?.channel), + channel: channel + ) + } +} + +private func getChannelIcon(_ channel: SAChannel?) -> IconResult? { + guard let channel = channel else { return nil } + @Singleton var getChannelIconUseCase: GetChannelBaseIconUseCase + + let subfunction: ThermostatSubfunction? = channel.isHvacThermostat() ? channel.value?.asThermostatValue().subfunction : nil + return getChannelIconUseCase.invoke(channel: channel, subfunction: subfunction) +} diff --git a/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesView.swift b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesView.swift new file mode 100644 index 00000000..b9472f6b --- /dev/null +++ b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermostatSlavesView.swift @@ -0,0 +1,265 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +import SwiftUI + +extension SwiftUI.View { + func listRowSeparatorInvisible() -> some View { + if #available(iOS 15.0, *) { + return self.listRowSeparator(.hidden) + } else { + return self + } + } +} + +extension ThermostatSlavesFeature { + struct View: SwiftUI.View { + @ObservedObject var viewState: ViewState + + let onInfoAction: (String) -> Void + let onStatusAction: (SAChannel?) -> Void + + var body: some SwiftUI.View { + BackgroundStack { + VStack { + if let master = viewState.master { + HeaderText(title: Strings.ThermostatDetail.mainThermostat) + ThermostatRow(data: master, onInfoAction: onInfoAction, onStatusAction: onStatusAction) + } + HeaderText(title: Strings.ThermostatDetail.otherThermostats) + .padding([.top], Dimens.distanceSmall) + LazyList(items: viewState.slaves) { + ThermostatRow(data: $0, onInfoAction: onInfoAction, onStatusAction: onStatusAction) + } + Spacer() + }.padding([.top], Dimens.distanceDefault) + }.environment(\.scaleFactor, viewState.scale) + } + } + + struct HeaderText: SwiftUI.View { + let title: String + var body: some SwiftUI.View { + HStack { + Text.BodyMedium(text: title.uppercased(), alignment: .leading) + Spacer() + }.padding([.leading, .trailing], Dimens.distanceSmall) + } + } + + struct ThermostatRow: SwiftUI.View { + @Environment(\.scaleFactor) var scaleFactor: CGFloat + + let data: ThermostatData + + let onInfoAction: (String) -> Void + let onStatusAction: (SAChannel?) -> Void + + var body: some SwiftUI.View { + ZStack { + HStack { + VStack(alignment: .center) { + ListItemIcon(iconResult: data.icon) + Text.BodySmall(text: data.currentPower ?? "") + }.padding([.leading], Dimens.distanceSmall) + VStack(alignment: .leading, spacing: scaleFactor.scale(Dimens.distanceSmall)) { + HStack { + Text.CellValue(text: data.value) + if (data.indicatorIcon == .off) { + Text.BodyMedium(text: "Off") + } else { + SetpointIndicator(icon: data.indicatorIcon) + if let subValue = data.subValue { + Text.BodyMedium(text: subValue) + } + } + ChildChannelIcon(icon: data.pumpSwitchIcon) + ChildChannelIcon(icon: data.sourceSwitchIcon) + } + Text.CellCaption(text: data.caption) + .padding([.trailing], Dimens.distanceSmall) + } + Spacer() + } + HStack(spacing: Dimens.distanceSmall) { + Spacer() + ListItemIssueIcon(icon: data.issueIconType) + .onTapGesture { + if let message = data.issueMessage { + onInfoAction(message) + } + } + if (data.showChannelStateIcon) { + ListItemInfoIcon() + .onTapGesture { onStatusAction(data.channel) } + } + ListItemDot(onlineState: data.onlineState) + }.padding([.trailing], Dimens.distanceSmall) + }.padding([.top, .bottom], Dimens.distanceTiny) + .background(Color.Supla.surface) + } + } + + struct ListItemIcon: SwiftUI.View { + @Environment(\.scaleFactor) var scaleFactor: CGFloat + + let iconResult: IconResult? + + var body: some SwiftUI.View { + Image() + .resizable() + .scaledToFit() + .frame(width: scale(scaleFactor, 60), height: scale(scaleFactor, 50)) + } + + private func Image() -> SwiftUI.Image { + if let iconResult = iconResult { + iconResult.image + } else { + SwiftUI.Image(.Icons.fncUnknown) + } + } + } + + struct ListItemInfoIcon: SwiftUI.View { + var body: some SwiftUI.View { + Image(.Icons.info) + .resizable() + .scaledToFit() + .frame(width: Dimens.iconInfoSize, height: Dimens.iconInfoSize) + .foregroundColor(.Supla.onBackground) + } + } + + struct ListItemIssueIcon: SwiftUI.View { + let icon: IssueIconType? + + var body: some SwiftUI.View { + if let icon = icon?.icon() { + Image(uiImage: icon) + .resizable() + .scaledToFit() + .frame(width: Dimens.iconSizeList, height: Dimens.iconSizeList) + .foregroundColor(.Supla.onBackground) + } + } + } + + struct ListItemDot: SwiftUI.View { + let onlineState: ListOnlineState + + var body: some SwiftUI.View { + let color = onlineState.online ? Color.Supla.primary : Color.Supla.error + ZStack { + Circle() + .stroke() + .fill(color) + .frame( + width: Dimens.ListItem.statusIndicatorSize, + height: Dimens.ListItem.statusIndicatorSize + ) + } + } + } + + struct SetpointIndicator: SwiftUI.View { + let icon: ThermostatIndicatorIcon? + + var body: some SwiftUI.View { + if let iconResource = icon?.resourceName { + Image(iconResource) + .resizable() + .scaledToFit() + .frame(width: 12, height: 12) + } + } + } + + struct ChildChannelIcon: SwiftUI.View { + let icon: IconResult? + + var body: some SwiftUI.View { + if let icon = icon { + icon.image + .resizable() + .scaledToFit() + .frame(width: Dimens.iconSize, height: Dimens.iconSize) + } + } + } +} + +#Preview { + let viewState = ThermostatSlavesFeature.ViewState() + viewState.master = ThermostatSlavesFeature.ThermostatData( + id: 1, + onlineState: .online, + caption: "FHC #0", + icon: .suplaIcon(name: "fnc_thermostat_heat"), + currentPower: nil, + value: "22,7°C", + indicatorIcon: .heating, + issueIconType: .warning, + issueMessage: nil, + showChannelStateIcon: true, + subValue: "23,0°", + pumpSwitchIcon: .suplaIcon(name: "fnc_pump_switch-on"), + sourceSwitchIcon: .suplaIcon(name: "fnc_heat_or_cold_source_switch-on"), + channel: nil + ) + viewState.slaves = [ + ThermostatSlavesFeature.ThermostatData( + id: 1, + onlineState: .online, + caption: "FHC #1", + icon: .suplaIcon(name: "fnc_thermostat_heat"), + currentPower: "25%", + value: "22,7°C", + indicatorIcon: .standby, + issueIconType: nil, + issueMessage: nil, + showChannelStateIcon: true, + subValue: "23,0°", + pumpSwitchIcon: .suplaIcon(name: "fnc_pump_switch-off"), + sourceSwitchIcon: .suplaIcon(name: "fnc_heat_or_cold_source_switch-on"), + channel: nil + ), + ThermostatSlavesFeature.ThermostatData( + id: 2, + onlineState: .online, + caption: "FHC #2", + icon: .suplaIcon(name: "fnc_thermostat_heat"), + currentPower: "100%", + value: "22,4°C", + indicatorIcon: .standby, + issueIconType: nil, + issueMessage: nil, + showChannelStateIcon: true, + subValue: "23,0°", + pumpSwitchIcon: .suplaIcon(name: "fnc_pump_switch-on"), + sourceSwitchIcon: .suplaIcon(name: "fnc_heat_or_cold_source_switch-off"), + channel: nil + ) + ] + return ThermostatSlavesFeature.View( + viewState: viewState, + onInfoAction: { _ in }, + onStatusAction: { _ in } + ) +} diff --git a/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermstatSlavesViewState.swift b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermstatSlavesViewState.swift new file mode 100644 index 00000000..092d642e --- /dev/null +++ b/SUPLA/Features/Details/ThermostatDetail/SlavesDetail/ThermstatSlavesViewState.swift @@ -0,0 +1,44 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extension ThermostatSlavesFeature { + class ViewState: ObservableObject { + @Published var master: ThermostatData? = nil + @Published var slaves: [ThermostatData] = [] + @Published var scale: CGFloat = 1 + } + + struct ThermostatData: Equatable, Identifiable { + let id: Int32 + let onlineState: ListOnlineState + let caption: String + let icon: IconResult? + let currentPower: String? + let value: String + let indicatorIcon: ThermostatIndicatorIcon + let issueIconType: IssueIconType? + let issueMessage: String? + let showChannelStateIcon: Bool + let subValue: String? + let pumpSwitchIcon: IconResult? + let sourceSwitchIcon: IconResult? + + // For legacy status popup + let channel: SAChannel? + } +} diff --git a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVC.swift b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVC.swift index 4b4630df..2e54768f 100644 --- a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVC.swift +++ b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVC.swift @@ -20,7 +20,6 @@ import Foundation import RxSwift class ThermostatGeneralVC: BaseViewControllerVM { - @Singleton private var formatter private let item: ItemBundle @@ -55,9 +54,7 @@ class ThermostatGeneralVC: BaseViewControllerVM { powerButtonView.tapObservable } var manualTapEvents: Observable { manualModeButtonView.tapObservable } var weeklyScheduleTapEvents: Observable { weeklyScheduleModeButtonView.tapObservable } @@ -303,6 +329,7 @@ fileprivate class ThermostatGeneralButtons: UIView { weeklyScheduleModeButtonView.isEnabled = newValue } } + var powerIconColor: UIColor { get { powerButtonView.iconColor } set { powerButtonView.iconColor = newValue } @@ -321,7 +348,7 @@ fileprivate class ThermostatGeneralButtons: UIView { private lazy var powerButtonView: RoundedControlButtonView = { let view = RoundedControlButtonView() view.translatesAutoresizingMaskIntoConstraints = false - view.icon = .suplaIcon(icon: .iconPowerButton) + view.icon = .suplaIcon(name: .Icons.powerButton) view.iconColor = .primary return view }() @@ -356,9 +383,7 @@ fileprivate class ThermostatGeneralButtons: UIView { setupView() } - override var intrinsicContentSize: CGSize { - get { CGSize(width: UIView.noIntrinsicMetric, height: 96)} - } + override var intrinsicContentSize: CGSize { CGSize(width: UIView.noIntrinsicMetric, height: 96) } private func setupView() { addSubview(powerButtonView) @@ -391,11 +416,8 @@ fileprivate class ThermostatGeneralButtons: UIView { // MARK: - Program view - -fileprivate class ProgramView: UIView { - - override var intrinsicContentSize: CGSize { - get { CGSize(width: UIView.noIntrinsicMetric, height: 20) } - } +private class ProgramView: UIView { + override var intrinsicContentSize: CGSize { CGSize(width: UIView.noIntrinsicMetric, height: 20) } var info: ThermostatProgramInfo? { get { nil } @@ -405,8 +427,9 @@ fileprivate class ProgramView: UIView { infoTypeLabel.text = info.type.text().uppercased() iconView.isHidden = info.icon == nil || info.iconColor == nil if let icon = info.icon, - let color = info.iconColor { - iconView.image = icon.withRenderingMode(.alwaysTemplate) + let color = info.iconColor + { + iconView.image = UIImage(named: icon)?.withRenderingMode(.alwaysTemplate) iconView.tintColor = color } descriptionLabel.isHidden = info.description == nil @@ -467,6 +490,7 @@ fileprivate class ProgramView: UIView { setupView() } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -533,8 +557,7 @@ fileprivate class ProgramView: UIView { } } -fileprivate class SensorIssueView: UIView { - +private class SensorIssueView: UIView { var icon: UIImage? { get { iconView.image } set { @@ -580,6 +603,7 @@ fileprivate class SensorIssueView: UIView { setupView() } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -596,7 +620,7 @@ fileprivate class SensorIssueView: UIView { setupChangeableConstraints() NSLayoutConstraint.activate([ - messageView.centerYAnchor.constraint(equalTo: centerYAnchor), + messageView.centerYAnchor.constraint(equalTo: centerYAnchor) ]) } diff --git a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVM.swift b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVM.swift index 0dfe48df..134488a5 100644 --- a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVM.swift +++ b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatGeneralVM.swift @@ -23,7 +23,7 @@ private let REFRESH_DELAY_S: Double = 3 class ThermostatGeneralVM: BaseViewModel { - @Singleton private var readChannelWithChildrenUseCase + @Singleton private var readChannelWithChildrenTreeUseCase @Singleton private var createTemperaturesListUseCase @Singleton private var getChannelConfigUseCase @Singleton private var getDeviceConfigUseCase @@ -95,12 +95,12 @@ class ThermostatGeneralVM: BaseViewModel ThermostatGeneralViewState { + private func handleFlags(_ state: ThermostatGeneralViewState, value: ThermostatValue, channelWithChildren: ChannelWithChildren, isOnline: Bool) -> ThermostatGeneralViewState { return state .changing( path: \.heatingIndicatorInactive, - to: !value.state.isOn() || !value.flags.contains(.heating) || !isOnline + to: !isActive(channelWithChildren, .heating) ) .changing( path: \.coolingIndicatorInactive, - to: !value.state.isOn() || !value.flags.contains(.cooling) || !isOnline + to: !isActive(channelWithChildren, .cooling) ) } + private func isActive(_ channelWithChildren: ChannelWithChildren, _ flag: SuplaThermostatFlag) -> Bool { + let children = channelWithChildren.allDescendantFlat.filter { $0.relationType == .masterThermostat } + let channelHasFlag = channelWithChildren.channel.isActive(flag) + + return if (children.isEmpty) { + channelHasFlag + } else { + channelHasFlag || children.reduce(false) { $0 || $1.channel.isActive(flag) } + } + } + + private func pumpSwitchIcon(_ channelWithChildren: ChannelWithChildren) -> IconResult? { + if let child = channelWithChildren.pumpSwitchChild { + return getChannelBaseIconUseCase.invoke(channel: child.channel) + } + + let switches = channelWithChildren.allDescendantFlat.filter({ $0.relationType == .pumpSwitch }) + if (switches.count == 1) { + return getChannelBaseIconUseCase.invoke(channel: switches.first!.channel) + } + + return nil + } + + private func heatOrColdSourceSwitchIcon(_ channelWithChildren: ChannelWithChildren) -> IconResult? { + if let child = channelWithChildren.heatOrColdSourceSwitchChild { + return getChannelBaseIconUseCase.invoke(channel: child.channel) + } + + let switches = channelWithChildren.allDescendantFlat.filter({ $0.relationType == .heatOrColdSourceSwitch }) + if (switches.count == 1) { + return getChannelBaseIconUseCase.invoke(channel: switches.first!.channel) + } + + return nil + } + private func handleButtons(_ state: ThermostatGeneralViewState, channel: SAChannel) -> ThermostatGeneralViewState { guard let value = channel.value?.asThermostatValue() else { return state } @@ -427,6 +468,12 @@ class ThermostatGeneralVM: BaseViewModel Bool { + guard let value = value?.asThermostatValue() else { return false } + return isOnline() && value.state.isOn() && value.flags.contains(flag) + } +} diff --git a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatProgramInfo.swift b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatProgramInfo.swift index c4670fcc..4fffee76 100644 --- a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatProgramInfo.swift +++ b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/ThermostatProgramInfo.swift @@ -19,7 +19,7 @@ struct ThermostatProgramInfo: Equatable { let type: InfoType let time: String? - let icon: UIImage? + let icon: String? let iconColor: UIColor? let description: String? let manualActive: Bool diff --git a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/UI/ThermometerValues.swift b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/UI/ThermometerValues.swift index 810b0fbd..8934a955 100644 --- a/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/UI/ThermometerValues.swift +++ b/SUPLA/Features/Details/ThermostatDetail/ThermostatGeneral/UI/ThermometerValues.swift @@ -72,7 +72,7 @@ final class ThermometerValues: UIStackView { private func measurementView(value: MeasurementValue, makeSmall: Bool) -> ThermometerValueView { let view = ThermometerValueView(small: makeSmall) view.translatesAutoresizingMaskIntoConstraints = false - view.icon = value.icon + view.icon = value.icon.uiImage view.label = value.value return view } diff --git a/SUPLA/Features/Details/ThermostatDetail/TimerDetail/ThermostatTimerDetailVC.swift b/SUPLA/Features/Details/ThermostatDetail/TimerDetail/ThermostatTimerDetailVC.swift index 23e09d86..0f03e0bb 100644 --- a/SUPLA/Features/Details/ThermostatDetail/TimerDetail/ThermostatTimerDetailVC.swift +++ b/SUPLA/Features/Details/ThermostatDetail/TimerDetail/ThermostatTimerDetailVC.swift @@ -143,7 +143,7 @@ class ThermostatTimerDetailVC: BaseViewControllerVM { @Singleton private var createProfileGroupsListUseCase @Singleton private var swapGroupPositionsUseCase - @Singleton private var provideDetailTypeUseCase + @Singleton private var provideDetailTypeUseCase @Singleton private var updateEventsManager @Singleton private var loadActiveProfileUrlUseCase @@ -53,12 +53,12 @@ class GroupListViewModel: BaseTableViewModel { } private func setActive(_ active: Bool) { - leftStatusIndicatorView.configure(filled: active, online: true) - rightStatusIndicatorView.configure(filled: active, online: true) + leftStatusIndicatorView.configure(filled: active, onlineState: .online) + rightStatusIndicatorView.configure(filled: active, onlineState: .online) } } diff --git a/SUPLA/Model/ChannelChild.swift b/SUPLA/Model/ChannelChild.swift index fb307ec7..cb6f57b0 100644 --- a/SUPLA/Model/ChannelChild.swift +++ b/SUPLA/Model/ChannelChild.swift @@ -21,4 +21,39 @@ import Foundation struct ChannelChild: Equatable { let channel: SAChannel let relationType: ChannelRelationType + let children: [ChannelChild] + + init(channel: SAChannel, relationType: ChannelRelationType, children: [ChannelChild] = []) { + self.channel = channel + self.relationType = relationType + self.children = children + } +} + +extension Array where Element == ChannelChild { + var indicatorIcon: ThermostatIndicatorIcon { + filter { $0.relationType == .masterThermostat } + .map { $0.channel.value?.asThermostatValue().indicatorIcon } + .compactMap { $0 } + .reduce(ThermostatIndicatorIcon.off) { result, value in value.moreImportantThan(result) ? value : result } + } + + var onlineState: ListOnlineState { + filter { $0.relationType == .masterThermostat } + .map { $0.channel.value?.online } + .compactMap { $0 } + .reduce(.unknown) { result, online in + if (result == .unknown && online) { + .online + } else if (result == .unknown) { + .offline + } else if (result == .online && !online) { + .partiallyOnline + } else if (result == .offline && online) { + .partiallyOnline + } else { + result + } + } + } } diff --git a/SUPLA/Model/ChannelWithChildren.swift b/SUPLA/Model/ChannelWithChildren.swift index 89a3b67a..9b86783d 100644 --- a/SUPLA/Model/ChannelWithChildren.swift +++ b/SUPLA/Model/ChannelWithChildren.swift @@ -21,6 +21,25 @@ import Foundation struct ChannelWithChildren: Equatable { let channel: SAChannel let children: [ChannelChild] + + var allDescendantFlat: [ChannelChild] { getChildren(children) } + + var pumpSwitchChild: ChannelChild? { children.first(where: { $0.relationType == .pumpSwitch }) } + + var heatOrColdSourceSwitchChild: ChannelChild? { + children.first(where: { $0.relationType == .heatOrColdSourceSwitch }) + } + + private func getChildren(_ tree: [ChannelChild]) -> [ChannelChild] { + var children: [ChannelChild] = [] + for item in tree { + children.append(item) + if (!item.children.isEmpty) { + children.append(contentsOf: getChildren(item.children)) + } + } + return children + } } extension ChannelWithChildren: BaseCellData { diff --git a/SUPLA/Model/Chart/HistoryDataSet.swift b/SUPLA/Model/Chart/HistoryDataSet.swift index bfeb7658..5d9bd306 100644 --- a/SUPLA/Model/Chart/HistoryDataSet.swift +++ b/SUPLA/Model/Chart/HistoryDataSet.swift @@ -20,7 +20,7 @@ import Foundation struct HistoryDataSet: Equatable, Changeable { let setId: Id - let icon: UIImage? + let icon: IconResult? let value: String let valueFormatter: ChannelValueFormatter let color: UIColor diff --git a/SUPLA/Model/CoreData/Extensions/SAChannel+Ext.swift b/SUPLA/Model/CoreData/Extensions/SAChannel+Ext.swift index ab5f0694..c582ad2d 100644 --- a/SUPLA/Model/CoreData/Extensions/SAChannel+Ext.swift +++ b/SUPLA/Model/CoreData/Extensions/SAChannel+Ext.swift @@ -17,6 +17,8 @@ */ extension SAChannel { + var onlineState: ListOnlineState { .from(isOnline()) } + func item() -> ItemBundle { ItemBundle(remoteId: remote_id, deviceId: device_id, subjectType: .channel, function: self.func) } diff --git a/SUPLA/Model/CoreData/Extensions/SAChannelBase+Ext.swift b/SUPLA/Model/CoreData/Extensions/SAChannelBase+Ext.swift index 2af6239b..f421af46 100644 --- a/SUPLA/Model/CoreData/Extensions/SAChannelBase+Ext.swift +++ b/SUPLA/Model/CoreData/Extensions/SAChannelBase+Ext.swift @@ -31,6 +31,20 @@ extension SAChannelBase { ) } + func isElectricityMeter() -> Bool { + self.func == SUPLA_CHANNELFNC_ELECTRICITY_METER + } + + func isImpulseCounter() -> Bool { + switch (self.func) { + case SUPLA_CHANNELFNC_IC_GAS_METER, + SUPLA_CHANNELFNC_IC_HEAT_METER, + SUPLA_CHANNELFNC_IC_WATER_METER, + SUPLA_CHANNELFNC_IC_ELECTRICITY_METER: true + default: false + } + } + func isThermometer() -> Bool { return self.func == SUPLA_CHANNELFNC_THERMOMETER || self.func == SUPLA_CHANNELFNC_HUMIDITYANDTEMPERATURE } diff --git a/SUPLA/Model/CoreData/Extensions/SAChannelValue+Ext.swift b/SUPLA/Model/CoreData/Extensions/SAChannelValue+Ext.swift index fc815b70..b644c2f5 100644 --- a/SUPLA/Model/CoreData/Extensions/SAChannelValue+Ext.swift +++ b/SUPLA/Model/CoreData/Extensions/SAChannelValue+Ext.swift @@ -20,7 +20,7 @@ import Foundation extension SAChannelValue { func asThermostatValue() -> ThermostatValue { - ThermostatValue.from(hvacValue: hvacValue()) + ThermostatValue.from(hvacValue(), online: online) } func asRollerShutterValue() -> RollerShutterValue { diff --git a/SUPLA/Model/IssueIconType.swift b/SUPLA/Model/IssueIconType.swift index 291096e7..5f797592 100644 --- a/SUPLA/Model/IssueIconType.swift +++ b/SUPLA/Model/IssueIconType.swift @@ -21,13 +21,13 @@ import Foundation enum IssueIconType { case warning case error - + func icon() -> UIImage? { - switch(self) { + switch (self) { case .warning: return .iconWarning case .error: - return.iconError + return .iconError } } } diff --git a/SUPLA/Model/SuplaClient/ChannelRelationType.swift b/SUPLA/Model/SuplaClient/ChannelRelationType.swift index 386583b2..84813b4d 100644 --- a/SUPLA/Model/SuplaClient/ChannelRelationType.swift +++ b/SUPLA/Model/SuplaClient/ChannelRelationType.swift @@ -29,6 +29,9 @@ enum ChannelRelationType: Int16, CaseIterable { case auxThermometerWater = 6 case auxThermometerGenericHeater = 7 case auxThermometerGenericCooler = 8 + case masterThermostat = 20 + case heatOrColdSourceSwitch = 21 + case pumpSwitch = 22 func isAux() -> Bool { switch (self) { diff --git a/SUPLA/Model/SuplaClient/Thermostat/SuplaHvacMode.swift b/SUPLA/Model/SuplaClient/Thermostat/SuplaHvacMode.swift index 5df7675b..dd04c944 100644 --- a/SUPLA/Model/SuplaClient/Thermostat/SuplaHvacMode.swift +++ b/SUPLA/Model/SuplaClient/Thermostat/SuplaHvacMode.swift @@ -30,11 +30,11 @@ public enum SuplaHvacMode: UInt8, CaseIterable { case cmdWeeklySchedule = 9 case cmdSwitchToManual = 10 - var icon: UIImage? { + var icon: String? { switch self { - case .off: return .iconPowerButton - case .heat: return .iconHeat - case .cool: return .iconCool + case .off: return .Icons.powerButton + case .heat: return .Icons.heat + case .cool: return .Icons.cool default: return nil } } diff --git a/SUPLA/Model/SuplaClient/Thermostat/SuplaThermostatFlag.swift b/SUPLA/Model/SuplaClient/Thermostat/SuplaThermostatFlag.swift index 2c7a0ab0..9833957b 100644 --- a/SUPLA/Model/SuplaClient/Thermostat/SuplaThermostatFlag.swift +++ b/SUPLA/Model/SuplaClient/Thermostat/SuplaThermostatFlag.swift @@ -31,6 +31,7 @@ enum SuplaThermostatFlag: UInt16, CaseIterable { case forcedOffBySensor = 9 case heatOrCool = 10 // if set cool else heat case weeklyScheduleTemporalOverride = 11 + case batterCoverOpen = 12 func value() -> UInt16 { 1 << rawValue } diff --git a/SUPLA/Model/SuplaClient/Thermostat/ThermostatIndicatorIcon.swift b/SUPLA/Model/SuplaClient/Thermostat/ThermostatIndicatorIcon.swift new file mode 100644 index 00000000..8a01ec26 --- /dev/null +++ b/SUPLA/Model/SuplaClient/Thermostat/ThermostatIndicatorIcon.swift @@ -0,0 +1,60 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +enum ThermostatIndicatorIcon { + case cooling, heating, forcedOffBySensor, standby, off, offline + + private var index: Int { + switch(self) { + case .cooling, .heating: 0 + case .forcedOffBySensor: 1 + case .standby: 2 + case .off: 3 + case .offline: 4 + } + } + + var resource: UIImage? { + switch(self) { + case .cooling: .iconCooling + case .heating: .iconHeating + case .forcedOffBySensor: .iconSensorAlert + case .standby: .iconStandby + default: nil + } + } + + var resourceName: String? { + switch(self) { + case .cooling: .Icons.cooling + case .heating: .Icons.heating + case .forcedOffBySensor: .Icons.sensorAlert + case .standby: .Icons.standby + default: nil + } + } + + func mergeWith(_ other: ThermostatIndicatorIcon?) -> ThermostatIndicatorIcon { + guard let other = other else { return self } + return index < other.index ? self : other + } + + func moreImportantThan(_ other: ThermostatIndicatorIcon) -> Bool { + index > other.index + } +} diff --git a/SUPLA/Model/Thermostat/MeasurementValue.swift b/SUPLA/Model/Thermostat/MeasurementValue.swift index 1462bccf..48ce1a53 100644 --- a/SUPLA/Model/Thermostat/MeasurementValue.swift +++ b/SUPLA/Model/Thermostat/MeasurementValue.swift @@ -19,6 +19,6 @@ import Foundation struct MeasurementValue: Equatable { - let icon: UIImage? + let icon: IconResult let value: String } diff --git a/SUPLA/Model/Thermostat/ThermostatValue.swift b/SUPLA/Model/Thermostat/ThermostatValue.swift index 025e184c..43cd0949 100644 --- a/SUPLA/Model/Thermostat/ThermostatValue.swift +++ b/SUPLA/Model/Thermostat/ThermostatValue.swift @@ -19,18 +19,77 @@ import Foundation struct ThermostatValue { + let online: Bool let state: ThermostatState let mode: SuplaHvacMode let setpointTemperatureHeat: Float let setpointTemperatureCool: Float let flags: [SuplaThermostatFlag] + + var subfunction: ThermostatSubfunction { flags.contains(.heatOrCool) ? .cool : .heat } + + var indicatorIcon: ThermostatIndicatorIcon { + if (!online) { + .offline + } else if (mode == .off) { + .off + } else if (flags.contains(.forcedOffBySensor)) { + .forcedOffBySensor + } else if (flags.contains(.cooling)) { + .cooling + } else if (flags.contains(.heating)) { + .heating + } else { + .standby + } + } + + var issueIcon: IssueIconType? { + if (online && flags.contains(.thermometerError)) { + .error + } else if (online && flags.contains(.batterCoverOpen)) { + .error + } else if (online && flags.contains(.clockError)) { + .warning + } else { + nil + } + } - var subfunction: ThermostatSubfunction { - get { flags.contains(.heatOrCool) ? .cool : .heat } + var issueText: String? { + if (flags.contains(.thermometerError)) { + return Strings.ThermostatDetail.thermometerError + } else if (flags.contains(.batterCoverOpen)) { + return Strings.ThermostatDetail.batteryCoverOpen + } else if (flags.contains(.clockError)) { + return Strings.ThermostatDetail.clockError + } else { + return nil + } + } + + var setpointText: String? { + if (!online) { + return "" + } + + @Singleton var formatter: ValuesFormatter + + switch (mode) { + case .cool: return formatter.temperatureToString(setpointTemperatureCool) + case .heat: return formatter.temperatureToString(setpointTemperatureHeat) + case .off: return "Off" + case .auto: + let min = formatter.temperatureToString(setpointTemperatureHeat) + let max = formatter.temperatureToString(setpointTemperatureCool) + return "\(min) - \(max)" + default: return "" + } } - static func from(hvacValue: THVACValue) -> ThermostatValue { + static func from(_ hvacValue: THVACValue, online: Bool) -> ThermostatValue { ThermostatValue( + online: online, state: ThermostatState(value: hvacValue.IsOn), mode: SuplaHvacMode.from(hvacMode: hvacValue.Mode), setpointTemperatureHeat: hvacValue.SetpointTemperatureHeat.fromSuplaTemperature(), @@ -42,7 +101,8 @@ struct ThermostatValue { struct ThermostatState { let value: UInt8 - + var power: Float? { value > 1 ? Float(value) - 1 : nil } + func isOn() -> Bool { value > 0 } func isOff() -> Bool { value == 0 } } diff --git a/SUPLA/Resources/Default.strings b/SUPLA/Resources/Default.strings index d16136eb..38c641c1 100644 --- a/SUPLA/Resources/Default.strings +++ b/SUPLA/Resources/Default.strings @@ -71,6 +71,8 @@ "channel_caption_curtain" = "Curtain"; "channel_caption_vertical_blind" = "Vertical blind"; "channel_caption_garage_door" = "Garage door"; +"channel_caption_pump_switch" = "Pump sitch"; +"channel_caption_heat_or_cold_source_switch" = "Heat or cold source switch"; /* Main */ "dialog_new_gesture_info_text" = "Swipe gesture to open details was removed, tap on particular channel to open it."; @@ -158,6 +160,7 @@ /* Thermostat Detail */ "thermostat_thermometer_error" = "Temperature reading error"; "thermostat_clock_error" = "Clock error"; +"thermostat_battery_cover_open" = "Battery cover open"; "thermostat_detail_mode_manual" = "Manual"; "thermostat_detail_mode_weekly_schedule" = "Program"; "schedule_detail_program_dialog_header" = "Program %d Settings"; @@ -174,6 +177,10 @@ "thermostat_detail_program_info" = "Tap to select program. Press long to modify."; "thermostat_detail_box_info" = "Tap to set selected program. Press long to set quarters."; "thermostat_detail_arrow_info" = "Black corner indicates current day and hour."; +"thermostat_detail_list" = "List"; +"thermostat_detail_main_thermostat" = "Main thermostat"; +"thermostat_detail_other_thermostats" = "Ohter thermostats"; + /* Combined Chart */ "history_range_label" = "Range"; diff --git a/SUPLA/Resources/Extensions/Font+Supla.swift b/SUPLA/Resources/Extensions/Font+Supla.swift index 5c4eb72f..f43f1f65 100644 --- a/SUPLA/Resources/Extensions/Font+Supla.swift +++ b/SUPLA/Resources/Extensions/Font+Supla.swift @@ -28,17 +28,29 @@ extension Font { static let headlineLarge: Font = .custom("OpenSans", size: 34) static let headlineMedium: Font = .custom("OpenSans", size: 24) static let headlineSmall: Font = .custom("OpenSans", size: 17) - + static let titleLarge: Font = .custom("OpenSans", size: 22) static let titleMedium: Font = .custom("OpenSans-SemiBold", size: 16) static let titleSmall: Font = .custom("OpenSans-SemiBold", size: 14) - + static let bodyLarge: Font = .custom("OpenSans", size: 16) static let bodyMedium: Font = .custom("OpenSans", size: 14) static let bodySmall: Font = .custom("OpenSans", size: 12) - + static let labelLarge: Font = .custom("OpenSans-Medium", size: 17) static let labelMedium: Font = .custom("OpenSans-SemiBold", size: 12) static let labelSmall: Font = .custom("OpenSans-SemiBold", size: 10) + + static func cellValue(_ scale: CGFloat, limit: CellScalingLimit = .none) -> Font { + .custom("Quicksand-Regular", size: scale.scale(Dimens.Fonts.value, limit: limit)) + } + + static func cellSubValue(_ scale: CGFloat, limit: CellScalingLimit = .none) -> Font { + .custom("OpenSans", size: scale.scale(Dimens.Fonts.label, limit: limit)) + } + + static func cellCaption(_ scale: CGFloat, limit: CellScalingLimit = .none) -> Font { + .custom("OpenSans-Bold", size: scale.scale(Dimens.Fonts.caption, limit: limit)) + } } } diff --git a/SUPLA/Resources/Extensions/String+Icons.swift b/SUPLA/Resources/Extensions/String+Icons.swift index 1b0baa68..0ea1237f 100644 --- a/SUPLA/Resources/Extensions/String+Icons.swift +++ b/SUPLA/Resources/Extensions/String+Icons.swift @@ -20,6 +20,8 @@ import Foundation extension String { + var uiImage: UIImage? { UIImage(named: self) } + struct Icons { // MARK: Bars static let general = "icon_general" @@ -137,6 +139,15 @@ extension String { static let fncThermostatDhw = "fnc_thermostat_dhw" // ShadingSystems static let fncTerraceAwning = "fnc_terrace_awning" + // Pump switch + static let fncPumpSwitch = "fnc_pump_switch" + // Heat or cold source switch + static let fncHeatOrColdSourceSwitch = "fnc_heat_or_cold_source_switch" + static let fncHeatOrColdSourceSwitch2 = "fnc_heat_or_cold_source_switch_2" + static let fncHeatOrColdSourceSwitch3 = "fnc_heat_or_cold_source_switch_3" + static let fncHeatOrColdSourceSwitch4 = "fnc_heat_or_cold_source_switch_4" + static let fncHeatOrColdSourceSwitch5 = "fnc_heat_or_cold_source_switch_5" + static let fncHeatOrColdSourceSwitch6 = "fnc_heat_or_cold_source_switch_6" // MARK: other static let thumbHeat = "thumb_heat" diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/Contents.json new file mode 100644 index 00000000..b14e8a8c --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "furnace_off.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "furnace_off_nm.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/furnace_off.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/furnace_off.svg new file mode 100644 index 00000000..e5d23783 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/furnace_off.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/furnace_off_nm.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/furnace_off_nm.svg new file mode 100644 index 00000000..1193a1b8 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-off.imageset/furnace_off_nm.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/Contents.json new file mode 100644 index 00000000..f10f888c --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "furnace_on.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "furnace_on_nm.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/furnace_on.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/furnace_on.svg new file mode 100644 index 00000000..9e38717e --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/furnace_on.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/furnace_on_nm.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/furnace_on_nm.svg new file mode 100644 index 00000000..dc0669da --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch-on.imageset/furnace_on_nm.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/Contents.json new file mode 100644 index 00000000..8ae08be2 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "gas_off.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "gas_off-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/gas_off-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/gas_off-1.svg new file mode 100644 index 00000000..dee68e28 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/gas_off-1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/gas_off.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/gas_off.svg new file mode 100644 index 00000000..678f9b0b --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-off.imageset/gas_off.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/Contents.json new file mode 100644 index 00000000..680f305c --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "gas_on.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "gas_on-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/gas_on-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/gas_on-1.svg new file mode 100644 index 00000000..98740efa --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/gas_on-1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/gas_on.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/gas_on.svg new file mode 100644 index 00000000..c2dea399 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_2-on.imageset/gas_on.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/Contents.json new file mode 100644 index 00000000..06c59a01 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "off.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "off-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/off-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/off-1.svg new file mode 100644 index 00000000..7b055e97 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/off-1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/off.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/off.svg new file mode 100644 index 00000000..f782ae99 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-off.imageset/off.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/Contents.json new file mode 100644 index 00000000..ee2ec330 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "on.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "on-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/on-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/on-1.svg new file mode 100644 index 00000000..d0bf7411 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/on-1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/on.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/on.svg new file mode 100644 index 00000000..ee534d16 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_3-on.imageset/on.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/Contents.json new file mode 100644 index 00000000..21c4eaaa --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heat pump_off.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heat pump_off-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/heat pump_off-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/heat pump_off-1.svg new file mode 100644 index 00000000..3d7f92c7 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/heat pump_off-1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/heat pump_off.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/heat pump_off.svg new file mode 100644 index 00000000..fe1d87b9 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-off.imageset/heat pump_off.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/Contents.json new file mode 100644 index 00000000..86dc40af --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heat pump_on.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heat pump_on-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/heat pump_on-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/heat pump_on-1.svg new file mode 100644 index 00000000..2af4aa46 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/heat pump_on-1.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/heat pump_on.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/heat pump_on.svg new file mode 100644 index 00000000..c38e32e1 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_4-on.imageset/heat pump_on.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/Contents.json new file mode 100644 index 00000000..3ee2e8df --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heat pump2_off.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heat pump2_off-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/heat pump2_off-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/heat pump2_off-1.svg new file mode 100644 index 00000000..f8f4fb28 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/heat pump2_off-1.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/heat pump2_off.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/heat pump2_off.svg new file mode 100644 index 00000000..fa90740a --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-off.imageset/heat pump2_off.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/Contents.json new file mode 100644 index 00000000..9fb9b0f5 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heat pump2_on.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heat pump2_on-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/heat pump2_on-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/heat pump2_on-1.svg new file mode 100644 index 00000000..9424b6d4 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/heat pump2_on-1.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/heat pump2_on.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/heat pump2_on.svg new file mode 100644 index 00000000..f6695b1f --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_5-on.imageset/heat pump2_on.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/Contents.json new file mode 100644 index 00000000..5a8dae3d --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heat pump 4_off.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heat pump 4_off-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/heat pump 4_off-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/heat pump 4_off-1.svg new file mode 100644 index 00000000..ef37085e --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/heat pump 4_off-1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/heat pump 4_off.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/heat pump 4_off.svg new file mode 100644 index 00000000..a131ca88 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-off.imageset/heat pump 4_off.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/Contents.json new file mode 100644 index 00000000..73092a6d --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heat pump 4_on.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heat pump 4_on-1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/heat pump 4_on-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/heat pump 4_on-1.svg new file mode 100644 index 00000000..1e5db2fc --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/heat pump 4_on-1.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/heat pump 4_on.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/heat pump 4_on.svg new file mode 100644 index 00000000..3d631532 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_heat_or_cold_source_switch_6-on.imageset/heat pump 4_on.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/Contents.json new file mode 100644 index 00000000..7816806a --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heating circulation pump on-1.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heating circulation pump on-3.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/heating circulation pump on-1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/heating circulation pump on-1.svg new file mode 100644 index 00000000..5ccaffdb --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/heating circulation pump on-1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/heating circulation pump on-3.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/heating circulation pump on-3.svg new file mode 100644 index 00000000..2f6c23e9 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-off.imageset/heating circulation pump on-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/Contents.json new file mode 100644 index 00000000..1bffbefd --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "heating circulation pump on.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "heating circulation pump on-2.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/heating circulation pump on-2.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/heating circulation pump on-2.svg new file mode 100644 index 00000000..d0d8973f --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/heating circulation pump on-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/heating circulation pump on.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/heating circulation pump on.svg new file mode 100644 index 00000000..dcb26497 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_pump_switch-on.imageset/heating circulation pump on.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/0 1.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/0 1.svg new file mode 100644 index 00000000..257dfc1d --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/0 1.svg @@ -0,0 +1,3 @@ + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/0.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/0.svg new file mode 100644 index 00000000..621e747d --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/0.svg @@ -0,0 +1,3 @@ + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/Contents.json index b9d77875..799dd1e7 100644 --- a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/Contents.json +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "unknown_channel@3x.png", + "filename" : "0.svg", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "unknown_channel-nm@3x 1.png", + "filename" : "0 1.svg", "idiom" : "universal" } ], diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/unknown_channel-nm@3x 1.png b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/unknown_channel-nm@3x 1.png deleted file mode 100644 index 7620604c..00000000 Binary files a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/unknown_channel-nm@3x 1.png and /dev/null differ diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/unknown_channel@3x.png b/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/unknown_channel@3x.png deleted file mode 100644 index e4ca1320..00000000 Binary files a/SUPLA/Resources/Resources.xcassets/Images/FunctionIconsOld/unknown_channel.imageset/unknown_channel@3x.png and /dev/null differ diff --git a/SUPLA/Resources/Strings.swift b/SUPLA/Resources/Strings.swift index 79088ab2..4c20f2de 100644 --- a/SUPLA/Resources/Strings.swift +++ b/SUPLA/Resources/Strings.swift @@ -231,6 +231,7 @@ struct Strings { struct ThermostatDetail { static let thermometerError = "thermostat_thermometer_error".toLocalized() static let clockError = "thermostat_clock_error".toLocalized() + static let batteryCoverOpen = "thermostat_battery_cover_open".toLocalized() static let modeManual = "thermostat_detail_mode_manual".toLocalized() static let modeWeeklySchedule = "thermostat_detail_mode_weekly_schedule".toLocalized() @@ -253,6 +254,10 @@ struct Strings { static let programInfo = "thermostat_detail_program_info".toLocalized() static let boxInfo = "thermostat_detail_box_info".toLocalized() static let arrowInfo = "thermostat_detail_arrow_info".toLocalized() + + static let list = "thermostat_detail_list".toLocalized() + static let mainThermostat = "thermostat_detail_main_thermostat".toLocalized() + static let otherThermostats = "thermostat_detail_other_thermostats".toLocalized() } struct Notifications { @@ -312,6 +317,8 @@ struct Strings { static let captionCurtain = "channel_caption_curtain".toLocalized() static let captionVerticalBlind = "channel_caption_vertical_blind".toLocalized() static let captionGarageDoor = "channel_caption_garage_door".toLocalized() + static let captionPumpSwitch = "channel_caption_pump_switch".toLocalized() + static let captionHeatOrCouldSourceSwitch = "channel_caption_heat_or_cold_source_switch".toLocalized() } } diff --git a/SUPLA/Resources/de.lproj/Localizable.strings b/SUPLA/Resources/de.lproj/Localizable.strings index 4fda47dd..79357c77 100644 --- a/SUPLA/Resources/de.lproj/Localizable.strings +++ b/SUPLA/Resources/de.lproj/Localizable.strings @@ -314,6 +314,8 @@ "channel_caption_curtain" = "Vorhang"; "channel_caption_vertical_blind" = "Vertikaljalousien"; "channel_caption_garage_door" = "Garagentor"; +"channel_caption_pump_switch" = "Pumpenschalter"; +"channel_caption_heat_or_cold_source_switch" = "Wärme- oder Kältequellenschalter"; /* Main */ "dialog_new_gesture_info_text" = "Wischgeste zum Öffnen der Kanaldetails wurde gelöscht, tippe auf dem Kanal, um sie zu sehen."; @@ -391,6 +393,7 @@ /* Thermostat Detail */ "thermostat_thermometer_error" = "Fehler beim Temperaturelesen"; "thermostat_clock_error" = "Uhrfehler"; +"thermostat_battery_cover_open" = "Batteriefachdeckel offen"; "thermostat_detail_mode_manual" = "Handbetrieb"; "thermostat_detail_mode_weekly_schedule" = "Wöchentlich"; "schedule_detail_program_dialog_header" = "Program %d Einstellungen"; @@ -407,6 +410,9 @@ "thermostat_detail_program_info" = "Tippen, um es zu aktivieren. Lang halten, um es zu ändern."; "thermostat_detail_box_info" = "Tippen, um das aktivierten Program zu setzen. Long halten, um die Viertelstunde anzupassen."; "thermostat_detail_arrow_info" = "Die dunkle Ecke zeigt aktuelles Tag und Zeit."; +"thermostat_detail_list" = "Liste"; +"thermostat_detail_main_thermostat" = "Hauptthermostat"; +"thermostat_detail_other_thermostats" = "Andere Thermostate"; /* Combined Chart */ "history_range_label" = "Bereich"; diff --git a/SUPLA/Resources/pl.lproj/Localizable.strings b/SUPLA/Resources/pl.lproj/Localizable.strings index 1081426e..8ee04b65 100644 --- a/SUPLA/Resources/pl.lproj/Localizable.strings +++ b/SUPLA/Resources/pl.lproj/Localizable.strings @@ -339,6 +339,8 @@ "channel_caption_curtain" = "Zasłona"; "channel_caption_vertical_blind" = "Żaluzja pionowa"; "channel_caption_garage_door" = "Brama garażowa"; +"channel_caption_pump_switch" = "Przełącznik pompy"; +"channel_caption_heat_or_cold_source_switch" = "Przełącznik źródła ciepła lub chłodu"; /* Main */ "dialog_new_gesture_info_text" = "Usunęliśmy gest przesunięcia otwierający szczegóły, aby je zobaczyć dotknij wybrany kanał."; @@ -421,6 +423,7 @@ /* Thermostat Detail */ "thermostat_thermometer_error" = "Błąd odczytu temperatury"; "thermostat_clock_error" = "Błąd zegara"; +"thermostat_battery_cover_open" = "Otwarta pokrywa baterii"; "thermostat_detail_mode_manual" = "Ręczny"; "thermostat_detail_mode_weekly_schedule" = "Program"; "schedule_detail_program_dialog_header" = "Ustawienia program %d"; @@ -437,6 +440,9 @@ "thermostat_detail_program_info" = "Dotknij, aby aktywować program. Przytrzymaj aby modyfikować program."; "thermostat_detail_box_info" = "Dotknij, aby ustawić wybrany program. Przytrzymaj, aby ustawić wybrane kwadranse."; "thermostat_detail_arrow_info" = "Czarny narożnik informuje o aktualnej godzinie i dniu."; +"thermostat_detail_list" = "Lista"; +"thermostat_detail_main_thermostat" = "Termostat główny"; +"thermostat_detail_other_thermostats" = "Lista pozostałych"; /* Combined Chart */ "history_range_label" = "Zakres"; diff --git a/SUPLA/SAElectricityMeterExtendedValue.m b/SUPLA/SAElectricityMeterExtendedValue.m index 6b11b919..2a82c310 100644 --- a/SUPLA/SAElectricityMeterExtendedValue.m +++ b/SUPLA/SAElectricityMeterExtendedValue.m @@ -19,11 +19,9 @@ #import "SAElectricityMeterExtendedValue.h" #import "supla-client.h" -_supla_int_t srpc_evtool_emev_v1to2(TElectricityMeter_ExtendedValue *v1, - TElectricityMeter_ExtendedValue_V2 *v2); @implementation SAElectricityMeterExtendedValue { - TElectricityMeter_ExtendedValue_V2 _emev; + TElectricityMeter_ExtendedValue_V3 _emev; } -(id)initWithExtendedValue:(SAChannelExtendedValue *)ev { @@ -34,7 +32,7 @@ -(id)initWithExtendedValue:(SAChannelExtendedValue *)ev { return nil; } -- (BOOL) getElectricityMeterExtendedValue:(TElectricityMeter_ExtendedValue_V2*)emev { +- (BOOL) getElectricityMeterExtendedValue:(TElectricityMeter_ExtendedValue_V3*)emev { if (emev == NULL) { return NO; } @@ -42,7 +40,7 @@ - (BOOL) getElectricityMeterExtendedValue:(TElectricityMeter_ExtendedValue_V2*)e __block BOOL result = NO; [self forEach:^BOOL(TSuplaChannelExtendedValue * _Nonnull ev) { - if (srpc_evtool_v2_extended2emextended(ev, emev)) { + if (srpc_evtool_v3_extended2emextended(ev, emev)) { result = YES; } return !result; diff --git a/SUPLA/UseCase/Channel/BaseLoadMeasurementsUseCase.swift b/SUPLA/UseCase/Channel/BaseLoadMeasurementsUseCase.swift index 5e916fc1..0c284a49 100644 --- a/SUPLA/UseCase/Channel/BaseLoadMeasurementsUseCase.swift +++ b/SUPLA/UseCase/Channel/BaseLoadMeasurementsUseCase.swift @@ -150,7 +150,7 @@ extension BaseLoadMeasurementsUseCase { } } - private func channelIcon(_ channel: SAChannel, _ type: ChartEntryType) -> UIImage? { + private func channelIcon(_ channel: SAChannel, _ type: ChartEntryType) -> IconResult? { @Singleton var formatter @Singleton var getChannelBaseIconUseCase diff --git a/SUPLA/UseCase/Channel/ReadChannelWithChildrenTreeUseCase.swift b/SUPLA/UseCase/Channel/ReadChannelWithChildrenTreeUseCase.swift new file mode 100644 index 00000000..3632ed1e --- /dev/null +++ b/SUPLA/UseCase/Channel/ReadChannelWithChildrenTreeUseCase.swift @@ -0,0 +1,76 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +import RxSwift + +protocol ReadChannelWithChildrenTreeUseCase { + func invoke(remoteId: Int32) -> Observable +} + +final class ReadChannelWithChildrenTreeUseCaseImpl: ReadChannelWithChildrenTreeUseCase { + @Singleton private var channelRepository + @Singleton private var profileRepository + @Singleton private var channelRelationRepository + + func invoke(remoteId: Int32) -> Observable { + return profileRepository + .getActiveProfile() + .flatMapFirst { profile in + Observable.zip( + self.channelRepository.getAllVisibleChannels(forProfile: profile), + self.channelRelationRepository.getParentsMap(for: profile), + resultSelector: { channels, listOfParents in + self.toTree(remoteId, channels, listOfParents) + } + ).flatMap { if let result = $0 { Observable.just(result) } else { Observable.empty() } } + } + } + + private func toTree(_ remoteId: Int32, _ channels: [SAChannel], _ parentsMap: [Int32: [SAChannelRelation]]) -> ChannelWithChildren? { + guard let channel = channels.first(where: { $0.remote_id == remoteId }) else { return nil } + var channelsMap: [Int32: SAChannel] = [:] + channels.forEach { channelsMap[$0.remote_id] = $0 } + + return ChannelWithChildren(channel: channel, children: findChildren(for: remoteId, parentsMap, channelsMap, [])) + } + + private func findChildren( + for channelId: Int32, + _ parentsMap: [Int32: [SAChannelRelation]], + _ channelsMap: [Int32: SAChannel], + _ childrenIds: [Int32] + ) -> [ChannelChild] { + var result: [ChannelChild] = [] + + parentsMap[channelId]?.forEach { + if let child = channelsMap[$0.channel_id] { + if (!childrenIds.contains(child.remote_id)) { + result.append( + ChannelChild( + channel: child, + relationType: $0.relationType, + children: findChildren(for: $0.channel_id, parentsMap, channelsMap, childrenIds + [channelId]) + ) + ) + } + } + } + + return result + } +} diff --git a/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift b/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift index 1d93f2b8..809c87a2 100644 --- a/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift +++ b/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift @@ -123,6 +123,10 @@ final class GetChannelBaseDefaultCaptionUseCaseImpl: GetChannelBaseDefaultCaptio return Strings.General.Channel.captionVerticalBlind case SUPLA_CHANNELFNC_ROLLER_GARAGE_DOOR: return Strings.General.Channel.captionGarageDoor + case SUPLA_CHANNELFNC_PUMPSWITCH: + return Strings.General.Channel.captionPumpSwitch + case SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH: + return Strings.General.Channel.captionHeatOrCouldSourceSwitch default: return NSLocalizedString("Not supported function", comment: "") } diff --git a/SUPLA/UseCase/ChannelBase/GetChannelBaseIconUseCase.swift b/SUPLA/UseCase/ChannelBase/GetChannelBaseIconUseCase.swift index 5bcb2136..95a480cf 100644 --- a/SUPLA/UseCase/ChannelBase/GetChannelBaseIconUseCase.swift +++ b/SUPLA/UseCase/ChannelBase/GetChannelBaseIconUseCase.swift @@ -16,6 +16,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +import SwiftUI + protocol GetChannelBaseIconUseCase { func invoke(iconData: IconData) -> IconResult } @@ -25,8 +27,8 @@ extension GetChannelBaseIconUseCase { channel: SAChannelBase, type: IconType = .single, subfunction: ThermostatSubfunction? = nil - ) -> UIImage? { - return invoke(iconData: channel.getIconData(type: type, subfunction: subfunction)).icon + ) -> IconResult { + return invoke(iconData: channel.getIconData(type: type, subfunction: subfunction)) } } @@ -46,7 +48,7 @@ final class GetChannelBaseIconUseCaseImpl: GetChannelBaseIconUseCase { let name = getDefaultIconNameUseCase.invoke(iconData: iconData) - return .suplaIcon(icon: .init(named: name)) + return .suplaIcon(name: name) } private func getUserIcon(_ function: Int32, _ userIcon: SAUserIcon?, _ channelState: ChannelState, _ iconType: IconType, _ subfunction: ThermostatSubfunction?) -> UIImage? { @@ -96,7 +98,7 @@ final class GetChannelBaseIconUseCaseImpl: GetChannelBaseIconUseCase { default: return channelState.isActive() ? userIcon?.getIcon(.icon2, darkMode: darkMode) : userIcon?.getIcon(.icon1, darkMode: darkMode) } - default: + default: return channelState.isActive() ? userIcon?.getIcon(.icon2, darkMode: darkMode) : userIcon?.getIcon(.icon1, darkMode: darkMode) } default: @@ -106,15 +108,29 @@ final class GetChannelBaseIconUseCaseImpl: GetChannelBaseIconUseCase { } enum IconResult: Equatable { - case suplaIcon(icon: UIImage?) + case suplaIcon(name: String) case userIcon(icon: UIImage?) } extension IconResult { - var icon: UIImage? { + var uiImage: UIImage? { switch (self) { - case .suplaIcon(let icon): return icon + case .suplaIcon(let name): return .init(named: name) case .userIcon(let icon): return icon } } + + var image: Image { + switch (self) { + case .suplaIcon(let name): + Image(name) + case .userIcon(let icon): + if let icon = icon { + Image(uiImage: icon) + } else { + Image(.Icons.fncUnknown) + } + } + } } + diff --git a/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift b/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift index 4200b3f4..f9755083 100644 --- a/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift +++ b/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift @@ -71,7 +71,9 @@ final class GetChannelBaseStateUseCaseImpl: GetChannelBaseStateUseCase { SUPLA_CHANNELFNC_THERMOSTAT_HEATPOL_HOMEPLUS, SUPLA_CHANNELFNC_HOTELCARDSENSOR, SUPLA_CHANNELFNC_ALARMARMAMENTSENSOR, - SUPLA_CHANNELFNC_LIGHTSWITCH: + SUPLA_CHANNELFNC_LIGHTSWITCH, + SUPLA_CHANNELFNC_PUMPSWITCH, + SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH: return valueWrapper.isClosed ? .on : .off case SUPLA_CHANNELFNC_DIMMER: return valueWrapper.brightness > 0 ? .on : .off @@ -120,7 +122,9 @@ final class GetChannelBaseStateUseCaseImpl: GetChannelBaseStateUseCase { SUPLA_CHANNELFNC_ALARMARMAMENTSENSOR, SUPLA_CHANNELFNC_LIGHTSWITCH, SUPLA_CHANNELFNC_DIMMER, - SUPLA_CHANNELFNC_RGBLIGHTING: .off + SUPLA_CHANNELFNC_RGBLIGHTING, + SUPLA_CHANNELFNC_PUMPSWITCH, + SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH: .off case SUPLA_CHANNELFNC_DIMMERANDRGBLIGHTING: .complex([.off, .off]) case SUPLA_CHANNELFNC_DIGIGLASS_HORIZONTAL, SUPLA_CHANNELFNC_DIGIGLASS_VERTICAL: .opaque diff --git a/SUPLA/UseCase/Detail/ProvideDetailTypeUseCase.swift b/SUPLA/UseCase/Detail/BaseDetailTypeProviderUseCase.swift similarity index 76% rename from SUPLA/UseCase/Detail/ProvideDetailTypeUseCase.swift rename to SUPLA/UseCase/Detail/BaseDetailTypeProviderUseCase.swift index 0c713a7b..724cfd43 100644 --- a/SUPLA/UseCase/Detail/ProvideDetailTypeUseCase.swift +++ b/SUPLA/UseCase/Detail/BaseDetailTypeProviderUseCase.swift @@ -18,12 +18,8 @@ import Foundation -protocol ProvideDetailTypeUseCase { - func invoke(channelBase: SAChannelBase) -> DetailType? -} - -final class ProvideDetailTypeUseCaseImpl: ProvideDetailTypeUseCase { - func invoke(channelBase: SAChannelBase) -> DetailType? { +class BaseDetailTypeProviderUseCase { + func provide(_ channelBase: SAChannelBase) -> DetailType? { switch channelBase.func { case SUPLA_CHANNELFNC_DIMMER, @@ -46,11 +42,6 @@ final class ProvideDetailTypeUseCaseImpl: ProvideDetailTypeUseCase { return .windowDetail(pages: [.verticalBlind]) case SUPLA_CHANNELFNC_ROLLER_GARAGE_DOOR: return .windowDetail(pages: [.garageDoor]) - case - SUPLA_CHANNELFNC_LIGHTSWITCH, - SUPLA_CHANNELFNC_POWERSWITCH, - SUPLA_CHANNELFNC_STAIRCASETIMER: - return .switchDetail(pages: getSwitchDetailPages(channelBase: channelBase)) case SUPLA_CHANNELFNC_ELECTRICITY_METER: return .legacy(type: .em) @@ -83,28 +74,6 @@ final class ProvideDetailTypeUseCaseImpl: ProvideDetailTypeUseCase { return nil } } - - private func getSwitchDetailPages(channelBase: SAChannelBase) -> [DetailPage] { - guard let channel = channelBase as? SAChannel - else { return [.switchGeneral] } - - var pages: [DetailPage] = [.switchGeneral] - - if channel.flags & Int64(SUPLA_CHANNEL_FLAG_COUNTDOWN_TIMER_SUPPORTED) > 0 && channel.func != SUPLA_CHANNELFNC_STAIRCASETIMER { - pages.append(.switchTimer) - } - - if let type = channel.value?.sub_value_type { - if type == SUBV_TYPE_IC_MEASUREMENTS { - pages.append(.historyIc) - } - if type == SUBV_TYPE_ELECTRICITY_MEASUREMENTS { - pages.append(.historyEm) - } - } - - return pages - } } enum DetailType: Equatable { @@ -129,6 +98,7 @@ enum DetailPage { // Thermostat case thermostatGeneral + case thermostatList case schedule case thermostatTimer case thermostatHistory diff --git a/SUPLA/UseCase/Detail/ProvideChannelDetailTypeUseCase.swift b/SUPLA/UseCase/Detail/ProvideChannelDetailTypeUseCase.swift new file mode 100644 index 00000000..1f339643 --- /dev/null +++ b/SUPLA/UseCase/Detail/ProvideChannelDetailTypeUseCase.swift @@ -0,0 +1,78 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +protocol ProvideChannelDetailTypeUseCase { + func invoke(channelWithChildren: ChannelWithChildren) -> DetailType? +} + +final class ProvideChannelDetailTypeUseCaseImpl: BaseDetailTypeProviderUseCase, ProvideChannelDetailTypeUseCase { + func invoke(channelWithChildren: ChannelWithChildren) -> DetailType? { + switch (channelWithChildren.channel.func) { + case SUPLA_CHANNELFNC_LIGHTSWITCH, + SUPLA_CHANNELFNC_POWERSWITCH, + SUPLA_CHANNELFNC_STAIRCASETIMER, + SUPLA_CHANNELFNC_PUMPSWITCH, + SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH: .switchDetail(pages: getSwitchDetailPages(channelWithChildren)) + + case SUPLA_CHANNELFNC_HVAC_THERMOSTAT: .thermostatDetail(pages: getThermostatDetailPages(channelWithChildren)) + + default: provide(channelWithChildren.channel) + } + } + + private func getSwitchDetailPages(_ channelWithChildren: ChannelWithChildren) -> [DetailPage] { + var list: [DetailPage] = [.switchGeneral] + + if (channelWithChildren.supportsTimer) { + list.append(.switchTimer) + } + + let meterChild = channelWithChildren.children.first(where: { $0.relationType == .meter }) + if (meterChild?.channel.isElectricityMeter() == true) { + list.append(.historyEm) + } else if (meterChild?.channel.isImpulseCounter() == true) { + list.append(.historyIc) + } else if (channelWithChildren.channel.value?.sub_value_type ?? 0 == SUBV_TYPE_IC_MEASUREMENTS) { + list.append(.historyIc) + } else if (channelWithChildren.channel.value?.sub_value_type ?? 0 == SUBV_TYPE_ELECTRICITY_MEASUREMENTS) { + list.append(.historyEm) + } + + return list + } + + private func getThermostatDetailPages(_ channelWithChildren: ChannelWithChildren) -> [DetailPage] { + var list: [DetailPage] = [.thermostatGeneral] + + if (channelWithChildren.children.first(where: { $0.relationType == .masterThermostat}) != nil) { + list.append(.thermostatList) + } + + list.append(.schedule) + list.append(.thermostatTimer) + list.append(.thermostatHistory) + + return list + } +} + +private extension ChannelWithChildren { + var supportsTimer: Bool { + channel.flags & Int64(SUPLA_CHANNEL_FLAG_COUNTDOWN_TIMER_SUPPORTED) > 0 && channel.func != SUPLA_CHANNELFNC_STAIRCASETIMER + } +} diff --git a/SUPLA/UseCase/Detail/ProvideGroupDetailTypeUseCase.swift b/SUPLA/UseCase/Detail/ProvideGroupDetailTypeUseCase.swift new file mode 100644 index 00000000..2b3a7442 --- /dev/null +++ b/SUPLA/UseCase/Detail/ProvideGroupDetailTypeUseCase.swift @@ -0,0 +1,27 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +protocol ProvideGroupDetailTypeUseCase { + func invoke(group: SAChannelGroup) -> DetailType? +} + +final class ProvideGroupDetailTypeUseCaseImpl: BaseDetailTypeProviderUseCase, ProvideGroupDetailTypeUseCase { + func invoke(group: SAChannelGroup) -> DetailType? { + provide(group) + } +} diff --git a/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift b/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift index d88c71e7..492ce90a 100644 --- a/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift +++ b/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift @@ -16,8 +16,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -public let CHANNEL_UNKNOWN_ICON_NAME = "unknown_channel" - protocol GetDefaultIconNameUseCase { func invoke(iconData: IconData) -> String } @@ -60,7 +58,7 @@ final class GetDefaultIconNameUseCaseImpl: GetDefaultIconNameUseCase { if let name = name { return name } else { - return CHANNEL_UNKNOWN_ICON_NAME + return .Icons.fncUnknown } } @@ -111,6 +109,8 @@ final class GetDefaultIconNameUseCaseImpl: GetDefaultIconNameUseCase { StaticIconNameProducer(function: SUPLA_CHANNELFNC_HOTELCARDSENSOR, name: "fnc_hotel_card"), AlarmArmamentIconNameProducer(), GeneralPurposeMeasurementIconNameProducer(), - GeneralPurposeMeterIconNameProducer() + GeneralPurposeMeterIconNameProducer(), + StaticIconNameProducer(function: SUPLA_CHANNELFNC_PUMPSWITCH, name: .Icons.fncPumpSwitch), + HeatOrColdSourceSwitchIconNameProducer() ] } diff --git a/SUPLA/UseCase/Icon/IconNameProducers/HeatOrColdSourceSwitchIconNameProducer.swift b/SUPLA/UseCase/Icon/IconNameProducers/HeatOrColdSourceSwitchIconNameProducer.swift new file mode 100644 index 00000000..a5943aae --- /dev/null +++ b/SUPLA/UseCase/Icon/IconNameProducers/HeatOrColdSourceSwitchIconNameProducer.swift @@ -0,0 +1,38 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +final class HeatOrColdSourceSwitchIconNameProducer: IconNameProducer { + func accepts(function: Int32) -> Bool { + function == SUPLA_CHANNELFNC_HEATORCOLDSOURCESWITCH + } + + func produce(iconData: IconData) -> String { + addStateSufix(name: altIcon(iconData.altIcon), state: iconData.state) + } + + private func altIcon(_ altIcon: Int32) -> String { + switch (altIcon) { + case 1: .Icons.fncHeatOrColdSourceSwitch2 + case 2: .Icons.fncHeatOrColdSourceSwitch3 + case 3: .Icons.fncHeatOrColdSourceSwitch4 + case 4: .Icons.fncHeatOrColdSourceSwitch5 + case 5: .Icons.fncHeatOrColdSourceSwitch6 + default: .Icons.fncHeatOrColdSourceSwitch + } + } +} diff --git a/SUPLA/UseCase/Icon/IconNameProducers/ThermostatIconNameProducer.swift b/SUPLA/UseCase/Icon/IconNameProducers/ThermostatIconNameProducer.swift index 6286fdf2..623ba5ba 100644 --- a/SUPLA/UseCase/Icon/IconNameProducers/ThermostatIconNameProducer.swift +++ b/SUPLA/UseCase/Icon/IconNameProducers/ThermostatIconNameProducer.swift @@ -24,18 +24,18 @@ final class ThermostatIconNameProducer: IconNameProducer { func produce(iconData: IconData) -> String { if (iconData.function == SUPLA_CHANNELFNC_HVAC_DOMESTIC_HOT_WATER) { - return "fnc_thermostat_dhw" + return .Icons.fncThermostatDhw } if let subfunction = iconData.subfunction { switch (subfunction) { - case .heat: return "fnc_thermostat_heat" - case .cool: return "fnc_thermostat_cool" - default: return CHANNEL_UNKNOWN_ICON_NAME + case .heat: return .Icons.fncThermostatHeat + case .cool: return .Icons.fncThermostatCool + default: return .Icons.fncUnknown } } - return CHANNEL_UNKNOWN_ICON_NAME + return .Icons.fncUnknown } diff --git a/SUPLA/UseCase/LegacyWrapper.swift b/SUPLA/UseCase/LegacyWrapper.swift index bad273a8..2fec6962 100644 --- a/SUPLA/UseCase/LegacyWrapper.swift +++ b/SUPLA/UseCase/LegacyWrapper.swift @@ -209,7 +209,7 @@ final class UseCaseLegacyWrapper: NSObject { @objc static func getChannelIcon(_ channel: SAChannelBase, _ iconType: IconType) -> UIImage? { @Singleton var getChannelBaseIconUseCase - return getChannelBaseIconUseCase.invoke(channel: channel, type: iconType) + return getChannelBaseIconUseCase.invoke(channel: channel, type: iconType).uiImage } @objc diff --git a/SUPLA/UseCase/Thermostat/CreateTemperaturesListUseCase.swift b/SUPLA/UseCase/Thermostat/CreateTemperaturesListUseCase.swift index bd77ac3c..fa721248 100644 --- a/SUPLA/UseCase/Thermostat/CreateTemperaturesListUseCase.swift +++ b/SUPLA/UseCase/Thermostat/CreateTemperaturesListUseCase.swift @@ -32,7 +32,7 @@ final class CreateTemperaturesListUseCaseImpl: CreateTemperaturesListUseCase { var result: [MeasurementValue] = [] if (children.filter({ $0.relationType == .mainThermometer }).isEmpty) { - result.append(MeasurementValue(icon: .fncUnknown, value: NO_VALUE_TEXT)) + result.append(MeasurementValue(icon: .suplaIcon(name: .Icons.fncUnknown), value: NO_VALUE_TEXT)) } for child in children { diff --git a/SUPLATests/Mocks/UseCase/ChannelBaseUseCasesMocks.swift b/SUPLATests/Mocks/UseCase/ChannelBaseUseCasesMocks.swift index bf43a0a9..455b17f2 100644 --- a/SUPLATests/Mocks/UseCase/ChannelBaseUseCasesMocks.swift +++ b/SUPLATests/Mocks/UseCase/ChannelBaseUseCasesMocks.swift @@ -29,7 +29,7 @@ final class GetChannelBaseStateUseCaseMock: GetChannelBaseStateUseCase { } final class GetChannelBaseIconUseCaseMock: GetChannelBaseIconUseCase { - var returns: IconResult = .suplaIcon(icon: nil) + var returns: IconResult = .suplaIcon(name: "") var parameters: [IconData] = [] func invoke(iconData: IconData) -> IconResult { parameters.append(iconData) diff --git a/SUPLATests/Mocks/UseCase/ChannelUseCasesMocks.swift b/SUPLATests/Mocks/UseCase/ChannelUseCasesMocks.swift index 27d06d06..21eb37fd 100644 --- a/SUPLATests/Mocks/UseCase/ChannelUseCasesMocks.swift +++ b/SUPLATests/Mocks/UseCase/ChannelUseCasesMocks.swift @@ -76,6 +76,15 @@ final class ReadChannelWithChildrenUseCaseMock: ReadChannelWithChildrenUseCase { } } +final class ReadChannelWithChildrenTreeUseCaseMock: ReadChannelWithChildrenTreeUseCase { + var returns: Observable = Observable.empty() + var parameters: [Int32] = [] + func invoke(remoteId: Int32) -> Observable { + parameters.append(remoteId) + return returns + } +} + final class DownloadChannelMeasurementsUseCaseMock: DownloadChannelMeasurementsUseCase { var parameters: [(Int32, Int32)] = [] func invoke(remoteId: Int32, function: Int32) { diff --git a/SUPLATests/Mocks/UseCase/DetailUseCasesMocks.swift b/SUPLATests/Mocks/UseCase/DetailUseCasesMocks.swift index ba8b3347..a241ce50 100644 --- a/SUPLATests/Mocks/UseCase/DetailUseCasesMocks.swift +++ b/SUPLATests/Mocks/UseCase/DetailUseCasesMocks.swift @@ -21,14 +21,22 @@ import RxSwift @testable import SUPLA -final class ProvideDetailTypeUseCaseMock: ProvideDetailTypeUseCase { - +final class ProvideChannelDetailTypeUseCaseMock: ProvideChannelDetailTypeUseCase { var detailType: DetailType? = nil - var channelBaseArray: [SAChannelBase] = [] - - func invoke(channelBase: SAChannelBase) -> DetailType? { - channelBaseArray.append(channelBase) - + var channelBaseArray: [ChannelWithChildren] = [] + + func invoke(channelWithChildren: ChannelWithChildren) -> DetailType? { + channelBaseArray.append(channelWithChildren) + return detailType + } +} + +final class ProvideGroupDetailTypeUseCaseMock: ProvideGroupDetailTypeUseCase { + var detailType: DetailType? = nil + var channelBaseArray: [SAChannelGroup] = [] + + func invoke(group: SAChannelGroup) -> DetailType? { + channelBaseArray.append(group) return detailType } } diff --git a/SUPLATests/Tests/Features/ChannelList/ChannelListVMTests.swift b/SUPLATests/Tests/Features/ChannelList/ChannelListVMTests.swift index b110c2af..8234a41f 100644 --- a/SUPLATests/Tests/Features/ChannelList/ChannelListVMTests.swift +++ b/SUPLATests/Tests/Features/ChannelList/ChannelListVMTests.swift @@ -31,19 +31,22 @@ class ChannelListVMTests: ViewModelTest { + + private lazy var useCase: ReadChannelWithChildrenTreeUseCase! = { ReadChannelWithChildrenTreeUseCaseImpl() }() + + private lazy var profileRepository: ProfileRepositoryMock! = { ProfileRepositoryMock() }() + private lazy var channelRepository: ChannelRepositoryMock! = { ChannelRepositoryMock() }() + private lazy var channelRelationRepository: ChannelRelationRepositoryMock! = { + ChannelRelationRepositoryMock() + }() + + override func setUp() { + DiContainer.shared.register(type: (any ProfileRepository).self, profileRepository!) + DiContainer.shared.register(type: (any ChannelRepository).self, channelRepository!) + DiContainer.shared.register(type: (any ChannelRelationRepository).self, channelRelationRepository!) + + super.setUp() + } + + override func tearDown() { + useCase = nil + profileRepository = nil + channelRepository = nil + channelRelationRepository = nil + super.tearDown() + } + + func test_shouldLoadChannelWithChildrenTree() { + // given + let channelId: Int32 = 1 + let profile = AuthProfileItem(testContext: nil) + let channels = [ + SAChannel.mock(1), + SAChannel.mock(2), + SAChannel.mock(3), + SAChannel.mock(4) + ] + + profileRepository.activeProfileObservable = Observable.just(profile) + channelRepository.allVisibleChannelsObservable = Observable.just(channels) + channelRelationRepository.getParentsMapReturns = Observable.just([ + 1: [SAChannelRelation.mock(1, channelId: 2, type: .meter), SAChannelRelation.mock(1, channelId: 3, type: .masterThermostat)], + 3: [SAChannelRelation.mock(3, channelId: 4, type: .mainThermometer)] + ]) + + // when + useCase.invoke(remoteId: channelId) + .subscribe(observer) + .disposed(by: disposeBag) + + // then + assertEventsCount(2) + assertEvents([ + .next( + ChannelWithChildren(channel: channels[0], children: [ + ChannelChild(channel: channels[1], relationType: .meter), + ChannelChild(channel: channels[2], relationType: .masterThermostat, children: [ + ChannelChild(channel: channels[3], relationType: .mainThermometer) + ]) + ]) + ), + .completed + ]) + + } +} diff --git a/SUPLATests/Tests/UseCase/ChannelBase/GetChannelBaseIconUseCaseTests.swift b/SUPLATests/Tests/UseCase/ChannelBase/GetChannelBaseIconUseCaseTests.swift index a73b822d..38c2859f 100644 --- a/SUPLATests/Tests/UseCase/ChannelBase/GetChannelBaseIconUseCaseTests.swift +++ b/SUPLATests/Tests/UseCase/ChannelBase/GetChannelBaseIconUseCaseTests.swift @@ -67,7 +67,7 @@ final class GetChannelBaseIconUseCaseTests: XCTestCase { // then XCTAssertNotNil(icon) - XCTAssertEqual(icon, .suplaIcon(icon: UIImage(named: "uv-on"))) + XCTAssertEqual(icon, .suplaIcon(name: "uv-on")) } func test_userIcon_activeState() { diff --git a/SUPLATests/Tests/UseCase/Detail/ProvideDetailTypeUseCaseTests.swift b/SUPLATests/Tests/UseCase/Detail/ProvideDetailTypeUseCaseTests.swift index 73877be1..6c88b91d 100644 --- a/SUPLATests/Tests/UseCase/Detail/ProvideDetailTypeUseCaseTests.swift +++ b/SUPLATests/Tests/UseCase/Detail/ProvideDetailTypeUseCaseTests.swift @@ -22,7 +22,7 @@ import XCTest final class ProvideDetailTypeUseCaseTests: XCTestCase { - private lazy var useCase: ProvideDetailTypeUseCase! = { ProvideDetailTypeUseCaseImpl() }() + private lazy var useCase: ProvideChannelDetailTypeUseCase! = { ProvideChannelDetailTypeUseCaseImpl() }() override func tearDown() { useCase = nil @@ -228,7 +228,7 @@ final class ProvideDetailTypeUseCaseTests: XCTestCase { func test_shouldProvideStandardDetailWithGeneralPage_forGroupOfSwitches() { doTest(expectedResult: .switchDetail(pages: [.switchGeneral])) { - let channel = SAChannelBase(testContext: nil) + let channel = SAChannel(testContext: nil) channel.func = SUPLA_CHANNELFNC_LIGHTSWITCH return channel @@ -318,12 +318,12 @@ final class ProvideDetailTypeUseCaseTests: XCTestCase { } } - private func doTest(expectedResult: DetailType?, provider: () -> SAChannelBase) { + private func doTest(expectedResult: DetailType?, provider: () -> SAChannel) { // given let channel = provider() // when - let type = useCase.invoke(channelBase: channel) + let type = useCase.invoke(channelWithChildren: ChannelWithChildren(channel: channel, children: [])) // then XCTAssertEqual(type, expectedResult) diff --git a/SUPLATests/Tests/UseCase/Thermostat/CreateTemperaturesListUseCaseTests.swift b/SUPLATests/Tests/UseCase/Thermostat/CreateTemperaturesListUseCaseTests.swift index 041a78dc..c9532ba1 100644 --- a/SUPLATests/Tests/UseCase/Thermostat/CreateTemperaturesListUseCaseTests.swift +++ b/SUPLATests/Tests/UseCase/Thermostat/CreateTemperaturesListUseCaseTests.swift @@ -56,9 +56,9 @@ final class CreateTemperaturesListUseCaseTests: XCTestCase { // then XCTAssertEqual(temperatures, [ - MeasurementValue(icon: nil, value: "---"), - MeasurementValue(icon: nil, value: "---"), - MeasurementValue(icon: nil, value: "---") + MeasurementValue(icon: .suplaIcon(name: ""), value: "---"), + MeasurementValue(icon: .suplaIcon(name: ""), value: "---"), + MeasurementValue(icon: .suplaIcon(name: ""), value: "---") ]) } @@ -74,7 +74,7 @@ final class CreateTemperaturesListUseCaseTests: XCTestCase { // then XCTAssertEqual(temperatures, [ - MeasurementValue(icon: nil, value: "---"), + MeasurementValue(icon: .suplaIcon(name: ""), value: "---"), ]) } @@ -96,8 +96,8 @@ final class CreateTemperaturesListUseCaseTests: XCTestCase { // then XCTAssertEqual(temperatures, [ - MeasurementValue(icon: nil, value: "23.4°"), - MeasurementValue(icon: nil, value: "0.0") + MeasurementValue(icon: .suplaIcon(name: ""), value: "23.4°"), + MeasurementValue(icon: .suplaIcon(name: ""), value: "0.0") ]) } @@ -110,7 +110,7 @@ final class CreateTemperaturesListUseCaseTests: XCTestCase { // then XCTAssertEqual(temperatures, [ - MeasurementValue(icon: .fncUnknown, value: "---") + MeasurementValue(icon: .suplaIcon(name: .Icons.fncUnknown), value: "---") ]) } }