From 5c3873830fae8e27202957a6e8cd48a50da68667 Mon Sep 17 00:00:00 2001 From: Adrian Philipp Date: Wed, 11 Nov 2020 15:18:37 +0100 Subject: [PATCH] Adds shot and long timer --- IthoEcoFanRFT.ino | 286 ++++++++++++++++++++++++++++++---------------- fan_accessory.c | 46 ++++++-- 2 files changed, 227 insertions(+), 105 deletions(-) diff --git a/IthoEcoFanRFT.ino b/IthoEcoFanRFT.ino index 2699a62..ad9f464 100644 --- a/IthoEcoFanRFT.ino +++ b/IthoEcoFanRFT.ino @@ -26,25 +26,28 @@ CC11xx pins ESP pins Arduino pins Description #include #include +#include #include "IthoCC1101.h" #include "IthoPacket.h" #include "wifi_info.h" #define LOG_D(fmt, ...) printf_P(PSTR(fmt "\n") , ##__VA_ARGS__); -typedef enum Speed +typedef enum State { - SpeedIgnore = 0, - SpeedStandby = 1, - SpeedLow = 2, - SpeedMedium = 3, - SpeedHigh = 4, - SpeedFull = 5 + StateIgnore = 0, + StateStandby = 1, + StateLow = 2, + StateMedium = 3, + StateHigh = 4, + StateFull = 5, + StateTimerShort = 6, + StateTimerLong = 7 }; -char *speed_to_string[] = {"ignore", "standby", "low", "medium", "high", "full"}; +char *state_to_string[] = {"ignore", "standby", "low", "medium", "high", "full", "short_timer", "long_timer"}; -typedef enum Action +typedef enum Action { ActionDeactivate = 1, ActionActivate = 2, @@ -52,20 +55,33 @@ typedef enum Action ActionSpeedLow = 4, ActionSpeedMedium = 5, ActionSpeedHigh = 6, - ActionSpeedFull = 7 + ActionSpeedFull = 7, + ActionTimerShortOn = 8, + ActionTimerShortOff = 9, + ActionTimerLongOn = 10, + ActionTimerLongOff = 11 }; -char *action_to_string[] = {"none", "deactivate", "activate", "standby", "low", "medium", "high", "full"}; +char *action_to_string[] = {"none", "deactivate", "activate", "standby", "low", "medium", "high", "full", "short_timer_on", "short_timer_off", "long_timer_on", "long_timer_off"}; IthoCC1101 rf; IthoPacket packet; -Speed current_speed = SpeedStandby; -Speed last_speed = SpeedStandby; +State current_state = StateLow; +State last_speed = StateLow; +Timer<1, millis> timer_short; +Timer<1, millis> timer_long; +int timer_short_ms = 3 * 60 * 1000; // 3 min +int timer_long_ms = 20 * 60 * 1000; // 20 min +//int timer_short_ms = 3 * 1000; +//int timer_long_ms = 10 * 1000; + // access the HomeKit characteristics defined in fan_accessory.c extern "C" homekit_server_config_t config; extern "C" homekit_characteristic_t ch_fan_active; extern "C" homekit_characteristic_t ch_fan_rotation_speed; +extern "C" homekit_characteristic_t ch_timer_short_on; +extern "C" homekit_characteristic_t ch_timer_long_on; static uint32_t next_heap_millis = 0; @@ -73,7 +89,7 @@ void setup(void) { Serial.begin(115200); wifi_connect(); - // homekit_storage_reset(); // to remove the previous HomeKit pairing storage when you first run this new HomeKit example + //homekit_storage_reset(); // to remove the previous HomeKit pairing storage when you first run this new HomeKit example homekit_setup(); delay(500); @@ -86,12 +102,14 @@ void setup(void) { void loop() { homekit_loop(); + timer_short.tick(); + timer_long.tick(); delay(10); } // Called when the fan value is enabled/disabled by iOS Home APP void ch_fan_active_setter(const homekit_value_t value) { - LOG_D("Active: %d", value.bool_value); + LOG_D("Homekit sends active: %d", value.bool_value); switch (value.bool_value) { case true: transition(ActionActivate); break; @@ -99,118 +117,203 @@ void ch_fan_active_setter(const homekit_value_t value) { } } +void ch_timer_short_setter(const homekit_value_t value) { + LOG_D("Homekit sends short timer: %d", value.bool_value); + + if (value.bool_value) { + transition(ActionTimerShortOn); + } else { + transition(ActionTimerShortOff); + } +} + +void ch_timer_long_setter(const homekit_value_t value) { + LOG_D("Homekit sends long timer: %d", value.bool_value); + + if (value.bool_value) { + transition(ActionTimerLongOn); + } else { + transition(ActionTimerLongOff); + } +} + // Called when the fan rotation speed is changed by iOS Home APP void ch_fan_rotation_speed_setter(const homekit_value_t value) { - LOG_D("Rotation speed: %.6f", value.float_value); + LOG_D("Homekit sends rotation speed: %.6f", value.float_value); - if (compare_float(value.float_value, 0.0)) { + if (compare_float(value.float_value, 0)) { transition(ActionSpeedStandby); - } else if (compare_float(value.float_value, 1)) { + } else if (compare_float(value.float_value, 25)) { transition(ActionSpeedLow); - } else if (compare_float(value.float_value, 2)) { + } else if (compare_float(value.float_value, 50)) { transition(ActionSpeedMedium); - } else if (compare_float(value.float_value, 3)) { + } else if (compare_float(value.float_value, 75)) { transition(ActionSpeedHigh); - } else if (compare_float(value.float_value, 4)) { + } else if (compare_float(value.float_value, 100)) { transition(ActionSpeedFull); } } -Speed get_next_speed(Speed current_speed, Action action) { - switch (current_speed) { - case SpeedStandby: +bool time_long_expired(void *) { + LOG_D("Long timer expired"); + transition(ActionTimerLongOff); + + return false; // to repeat the action - false to stop +} + +bool time_short_expired(void *) { + LOG_D("Short timer expired"); + transition(ActionTimerShortOff); + + return false; // to repeat the action - false to stop +} + +// Determine what speed to set. +// Homekit often sends 'rotation speed' and 'active' at the same time. +// The state machine avoids invalid transitions. +State get_next_state(State current_state, Action action) { + switch (current_state) { + case StateStandby: switch (action) { - case ActionActivate: return last_speed != SpeedStandby ? last_speed : SpeedLow; - case ActionSpeedLow: return SpeedLow; - case ActionSpeedMedium: return SpeedMedium; - case ActionSpeedHigh: return SpeedHigh; - case ActionSpeedFull: return SpeedFull; + case ActionActivate: return last_speed != StateStandby ? last_speed : StateLow; + case ActionSpeedLow: return StateLow; + case ActionSpeedMedium: return StateMedium; + case ActionSpeedHigh: return StateHigh; + case ActionSpeedFull: return StateFull; + case ActionTimerShortOn: return StateTimerShort; + case ActionTimerLongOn: return StateTimerLong; } break; - case SpeedLow: + case StateLow: switch (action) { - case ActionDeactivate: return SpeedStandby; - case ActionSpeedMedium: return SpeedMedium; - case ActionSpeedHigh: return SpeedHigh; - case ActionSpeedFull: return SpeedFull; + case ActionDeactivate: return StateStandby; + case ActionSpeedMedium: return StateMedium; + case ActionSpeedHigh: return StateHigh; + case ActionSpeedFull: return StateFull; + case ActionTimerShortOn: return StateTimerShort; + case ActionTimerLongOn: return StateTimerLong; } break; - case SpeedMedium: + case StateMedium: switch (action) { - case ActionDeactivate: return SpeedStandby; - case ActionSpeedLow: return SpeedLow; - case ActionSpeedHigh: return SpeedHigh; - case ActionSpeedFull: return SpeedFull; + case ActionDeactivate: return StateStandby; + case ActionSpeedLow: return StateLow; + case ActionSpeedHigh: return StateHigh; + case ActionSpeedFull: return StateFull; + case ActionTimerShortOn: return StateTimerShort; + case ActionTimerLongOn: return StateTimerLong; } break; - case SpeedHigh: + case StateHigh: switch (action) { - case ActionDeactivate: return SpeedStandby; - case ActionSpeedLow: return SpeedLow; - case ActionSpeedMedium: return SpeedMedium; - case ActionSpeedFull: return SpeedFull; + case ActionDeactivate: return StateStandby; + case ActionSpeedLow: return StateLow; + case ActionSpeedMedium: return StateMedium; + case ActionSpeedFull: return StateFull; + case ActionTimerShortOn: return StateTimerShort; + case ActionTimerLongOn: return StateTimerLong; } break; - case SpeedFull: + case StateFull: switch (action) { - case ActionDeactivate: return SpeedStandby; - case ActionSpeedLow: return SpeedLow; - case ActionSpeedMedium: return SpeedMedium; - case ActionSpeedHigh: return SpeedHigh; + case ActionDeactivate: return StateStandby; + case ActionSpeedLow: return StateLow; + case ActionSpeedMedium: return StateMedium; + case ActionSpeedHigh: return StateHigh; + case ActionTimerShortOn: return StateTimerShort; + case ActionTimerLongOn: return StateTimerLong; } break; + + case StateTimerShort: + switch (action) { + case ActionTimerShortOn: return StateTimerShort; // Restart the timer if turned on again + case ActionTimerShortOff: timer_short.cancel(); return last_speed; // When the timer ends or is disabled, go back to the previous speed + case ActionTimerLongOn: timer_short.cancel(); return StateTimerLong; // A short timer can be overwritten by a long timer + // No other action can overwrite the timer + } + break; + + case StateTimerLong: + switch (action) { + case ActionTimerLongOn: return StateTimerLong; // Restart the timer if turned on again + case ActionTimerLongOff: timer_long.cancel(); return last_speed; // When the timer ends or is disabled, go back to the previous speed + // No other action can overwrite the timer + } + break; } - return SpeedIgnore; + return StateIgnore; } void transition(Action action) { - Speed next_speed = get_next_speed(current_speed, action); + State next_state = get_next_state(current_state, action); - LOG_D("Transition. Current speed: %s, action: %s, next speed: %s", - speed_to_string[current_speed], + LOG_D("Transition. Current state: '%s', action: '%s', next state: '%s'", + state_to_string[current_state], action_to_string[action], - speed_to_string[next_speed]); - - if (next_speed == SpeedIgnore || current_speed == next_speed) { + state_to_string[next_state]); + + if (next_state == StateIgnore || current_state == next_state) { LOG_D("Already good. Skipping transition."); - + return; } // Change internal state - last_speed = current_speed; - current_speed = next_speed; + current_state = next_state; + if (next_state >= 1 && next_state <= 5) { // only set "speed" states + last_speed = current_state; + } - // Change Homekit state - ch_fan_active.value.bool_value = next_speed != SpeedStandby; - ch_fan_rotation_speed.value.int_value = next_speed - 1; - - // Change fan speed state - if (next_speed == SpeedStandby) { + // Change Homekit active state + ch_fan_active.value.bool_value = current_state != StateStandby; + homekit_characteristic_notify(&ch_fan_active, ch_fan_active.value); + + // Set Homekit timer state + ch_timer_short_on.value.bool_value = current_state == StateTimerShort; + ch_timer_long_on.value.bool_value = current_state == StateTimerLong; + homekit_characteristic_notify(&ch_timer_short_on, ch_timer_short_on.value); + homekit_characteristic_notify(&ch_timer_long_on, ch_timer_long_on.value); + + // Change fan speed state and Homekit rotation speed + if (current_state == StateStandby) { sendStandbySpeed(); - } else if (next_speed == SpeedLow) { + ch_fan_rotation_speed.value.float_value = 0.0; + } else if (current_state == StateLow) { sendLowSpeed(); - } else if (next_speed == SpeedMedium) { + ch_fan_rotation_speed.value.float_value = 25.0; + } else if (current_state == StateMedium || current_state == StateTimerShort) { sendMediumSpeed(); - } else if (next_speed == SpeedHigh) { + ch_fan_rotation_speed.value.float_value = 50.0; + } else if (current_state == StateHigh || current_state == StateTimerLong) { sendHighSpeed(); - } else if (next_speed == SpeedFull) { + ch_fan_rotation_speed.value.float_value = 75.0; + } else if (current_state == StateFull) { sendFullSpeed(); + ch_fan_rotation_speed.value.float_value = 100.0; + } + homekit_characteristic_notify(&ch_fan_rotation_speed, ch_fan_rotation_speed.value); + + // Start or restart timers + if (action == ActionTimerShortOn) { + timer_short.in(timer_short_ms, time_short_expired); + } else if (action == ActionTimerLongOn) { + timer_long.in(timer_long_ms, time_long_expired); } } void homekit_setup() { ch_fan_active.setter = ch_fan_active_setter; ch_fan_rotation_speed.setter = ch_fan_rotation_speed_setter; + ch_timer_short_on.setter = ch_timer_short_setter; + ch_timer_long_on.setter = ch_timer_long_setter; + arduino_homekit_setup(&config); - - //report the switch value to HomeKit if it is changed (e.g. by a physical button) - //homekit_characteristic_notify(&ch_fan_active, ch_fan_active.value); } void homekit_loop() { @@ -223,16 +326,10 @@ void homekit_loop() { } } -//void sendCommand(IthoCommand command) { -// LOG_D("Sending %d...", command) -// rf.sendCommand(command); -// LOG_D("Sending %d done", command) -//} - void sendRegister() { - Serial.println("sending join..."); - rf.sendCommand(IthoJoin); - Serial.println("sending join done."); + Serial.println("sending join..."); + rf.sendCommand(IthoJoin); + Serial.println("sending join done."); } void sendStandbySpeed() { @@ -241,17 +338,16 @@ void sendStandbySpeed() { Serial.println("sending standby done."); } - void sendLowSpeed() { - Serial.println("sending low..."); - rf.sendCommand(IthoLow); - Serial.println("sending low done."); + Serial.println("sending low..."); + rf.sendCommand(IthoLow); + Serial.println("sending low done."); } void sendMediumSpeed() { - Serial.println("sending medium..."); - rf.sendCommand(IthoMedium); - Serial.println("sending medium done."); + Serial.println("sending medium..."); + rf.sendCommand(IthoMedium); + Serial.println("sending medium done."); } void sendHighSpeed() { @@ -261,18 +357,12 @@ void sendHighSpeed() { } void sendFullSpeed() { - Serial.println("sending FullSpeed..."); - rf.sendCommand(IthoFull); - Serial.println("sending FullSpeed done."); -} - -void sendTimer() { - Serial.println("sending timer..."); - rf.sendCommand(IthoTimer1); - Serial.println("sending timer done."); + Serial.println("sending FullSpeed..."); + rf.sendCommand(IthoFull); + Serial.println("sending FullSpeed done."); } - int compare_float(float f1, float f2) +int compare_float(float f1, float f2) { float precision = 0.00001; if (((f1 - precision) < f2) && diff --git a/fan_accessory.c b/fan_accessory.c index c9691a3..9540e32 100644 --- a/fan_accessory.c +++ b/fan_accessory.c @@ -7,12 +7,18 @@ void my_accessory_identify(homekit_value_t _value) { } homekit_characteristic_t ch_fan_active = HOMEKIT_CHARACTERISTIC_(ACTIVE, 0); -homekit_characteristic_t ch_fan_rotation_speed = HOMEKIT_CHARACTERISTIC_(ROTATION_SPEED, 0, .max_value = (float[]) {4}); +homekit_characteristic_t ch_fan_rotation_speed = HOMEKIT_CHARACTERISTIC_(ROTATION_SPEED, 0, .min_step = (float[]) {25}); + +homekit_characteristic_t ch_timer_short_name = HOMEKIT_CHARACTERISTIC_(NAME, "Korte timer"); +homekit_characteristic_t ch_timer_short_on = HOMEKIT_CHARACTERISTIC_(ON, false); + +homekit_characteristic_t ch_timer_long_name = HOMEKIT_CHARACTERISTIC_(NAME, "Lange timer"); +homekit_characteristic_t ch_timer_long_on = HOMEKIT_CHARACTERISTIC_(ON, false); homekit_accessory_t *accessories[] = { HOMEKIT_ACCESSORY(.id=1, .category=homekit_accessory_category_fan, .services=(homekit_service_t*[]) { HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]) { - HOMEKIT_CHARACTERISTIC(NAME, "Ventilatie"), + HOMEKIT_CHARACTERISTIC(NAME, "Ventilatie 3"), HOMEKIT_CHARACTERISTIC(MANUFACTURER, "Itho CVE"), HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "123-456-789"), HOMEKIT_CHARACTERISTIC(MODEL, "IthoCVE ECO RFT Ventilatie"), @@ -20,12 +26,38 @@ homekit_accessory_t *accessories[] = { HOMEKIT_CHARACTERISTIC(IDENTIFY, my_accessory_identify), NULL }), - HOMEKIT_SERVICE(FAN2, .primary=true, .characteristics=(homekit_characteristic_t*[]){ - HOMEKIT_CHARACTERISTIC(NAME, "Ventilatie"), - &ch_fan_active, - &ch_fan_rotation_speed, - NULL + HOMEKIT_SERVICE(FAN2, .primary=true, .characteristics=(homekit_characteristic_t*[]){ + HOMEKIT_CHARACTERISTIC(NAME, "Ventilatie 3"), + &ch_fan_active, + &ch_fan_rotation_speed, + NULL + }), + NULL + }), + HOMEKIT_ACCESSORY(.id=2, .category=homekit_accessory_category_switch, .services=(homekit_service_t*[]) { + HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]) { + HOMEKIT_CHARACTERISTIC(NAME, "Toilet timer"), + HOMEKIT_CHARACTERISTIC(IDENTIFY, my_accessory_identify), + NULL + }), + HOMEKIT_SERVICE(SWITCH, .primary=true, .characteristics=(homekit_characteristic_t*[]){ + &ch_timer_short_name, + &ch_timer_short_on, + NULL + }), + NULL }), + HOMEKIT_ACCESSORY(.id=3, .category=homekit_accessory_category_switch, .services=(homekit_service_t*[]) { + HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]) { + HOMEKIT_CHARACTERISTIC(NAME, "Douche timer"), + HOMEKIT_CHARACTERISTIC(IDENTIFY, my_accessory_identify), + NULL + }), + HOMEKIT_SERVICE(SWITCH, .primary=false, .characteristics=(homekit_characteristic_t*[]){ + &ch_timer_long_name, + &ch_timer_long_on, + NULL + }), NULL }), NULL