├── .gitattributes ├── HA_configuration.yaml ├── README.md ├── itho.h └── itho.yaml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /HA_configuration.yaml: -------------------------------------------------------------------------------- 1 | fan: 2 | - platform: template 3 | fans: 4 | afzuiging_badkamer: 5 | friendly_name: "Afzuiging badkamer" 6 | value_template: > 7 | {{ "off" if states('sensor.fanspeed') == 'Low' else "on" }} 8 | percentage_template: > 9 | {% set speedperc = {'Low': 0, 'Medium': 50, 'High': 100} %} 10 | {{speedperc [states('sensor.fanspeed')]}} 11 | turn_on: 12 | service: switch.turn_on 13 | data: 14 | entity_id: switch.fansendhigh 15 | turn_off: 16 | service: switch.turn_on 17 | data: 18 | entity_id: switch.fansendlow 19 | set_percentage: 20 | service: switch.turn_on 21 | data: 22 | entity_id: > 23 | {% set id_mapp = {0:'switch.fansendlow',50:'switch.fansendmedium', 100:'switch.fansendhigh'} %} 24 | {{id_mapp[percentage]}} 25 | speed_count: 2 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESPHOME-ITHO 2 | ESPHOME version of the ESPEASY-ITHO plugin. This is code i reworked form https://github.com/CoMPaTech/esphome_c1101 3 | The code missed some things i had available in the ESPEASY ITHO plugin 4 | 5 | Added: 6 | - Timer value is counting down when a timer function is activated 7 | - Extra textsensor to identify the device that had issued the state change of the fan. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /itho.h: -------------------------------------------------------------------------------- 1 | #include "esphome.h" 2 | #include "IthoCC1101.h" 3 | 4 | 5 | //List of States: 6 | 7 | // 1 - Itho ventilation unit to lowest speed 8 | // 2 - Itho ventilation unit to medium speed 9 | // 3 - Itho ventilation unit to high speed 10 | // 4 - Itho ventilation unit to full speed 11 | // 13 -Itho to high speed with hardware timer (10 min) 12 | // 23 -Itho to high speed with hardware timer (20 min) 13 | // 33 -Itho to high speed with hardware timer (30 min) 14 | 15 | typedef struct { String Id; String Roomname; } IdDict; 16 | 17 | // Global struct to store Names, should be changed in boot call,to set user specific 18 | IdDict Idlist[] = { {"ID1", "Controller Room1"}, 19 | {"ID2", "Controller Room2"}, 20 | {"ID3", "Controller Room3"} 21 | }; 22 | IthoCC1101 rf; 23 | void ITHOinterrupt() IRAM_ATTR; 24 | void ITHOcheck(); 25 | 26 | // extra for interrupt handling 27 | bool ITHOhasPacket = false; 28 | 29 | // init states 30 | int State=1; // after startup it is assumed that the fan is running low 31 | int OldState=1; 32 | int Timer=0; 33 | 34 | // init ID's 35 | String LastID; 36 | String OldLastID; 37 | String Mydeviceid = "ESPHOME"; // should be changed in boot call,to set user specific 38 | 39 | long LastPublish=0; 40 | bool InitRunned = false; 41 | 42 | // Timer values for hardware timer in Fan 43 | #define Time1 10*60 44 | #define Time2 20*60 45 | #define Time3 30*60 46 | 47 | 48 | TextSensor *InsReffanspeed; // Used for referencing outside FanRecv Class 49 | 50 | String TextSensorfromState(int currentState) 51 | { 52 | switch (currentState) 53 | { 54 | case 1: 55 | return "Low"; 56 | break; 57 | case 2: 58 | return "Medium"; 59 | break; 60 | case 3: 61 | return "High"; 62 | break; 63 | case 13: case 23: case 33: 64 | return "High(T)"; 65 | break; 66 | case 4: 67 | return "Full"; 68 | break; 69 | default: 70 | return "Unknow"; 71 | break; 72 | } 73 | } 74 | 75 | 76 | class FanRecv : public PollingComponent { 77 | public: 78 | 79 | // Publish 3 sensors 80 | // The state of the fan, Timer value and Last controller that issued the current state 81 | TextSensor *fanspeed = new TextSensor(); 82 | // Timer left (though this is indicative) when pressing the timer button once, twice or three times 83 | TextSensor *fantimer = new TextSensor(); 84 | // Last id that has issued the current state 85 | TextSensor *Lastid = new TextSensor(); 86 | 87 | // Update timer 1 second 88 | FanRecv() : PollingComponent(1000) { } 89 | 90 | void setup() 91 | { 92 | InsReffanspeed = this->fanspeed; // Make textsensor outside class available, so it can be used in Interrupt Service Routine 93 | rf.init(); 94 | // Followin wiring schema, change PIN if you wire differently 95 | pinMode(D1, INPUT); 96 | attachInterrupt(D1, ITHOinterrupt, FALLING); 97 | rf.initReceive(); 98 | InitRunned = true; 99 | } 100 | void loop() override 101 | { 102 | if (ITHOhasPacket) // When Signal (from ISR) packet received, process packet 103 | { 104 | ITHOhasPacket= false; 105 | ITHOcheck(); 106 | } 107 | } 108 | void update() override 109 | { 110 | if (State >= 10) 111 | { 112 | Timer--; 113 | } 114 | 115 | if ((State >= 10) && (Timer <= 0)) 116 | { 117 | State = 1; 118 | Timer = 0; 119 | fantimer->publish_state(String(Timer).c_str()); // this ensures that times value 0 is published when elapsed 120 | } 121 | //Publish new data when vars are changed or timer is running 122 | if ((OldState != State) || (Timer > 0)|| InitRunned) 123 | { 124 | fanspeed->publish_state(TextSensorfromState(State).c_str()); 125 | fantimer->publish_state(String(Timer).c_str()); 126 | Lastid->publish_state(LastID.c_str()); 127 | OldState = State; 128 | InitRunned = false; 129 | } 130 | } 131 | }; 132 | 133 | // Figure out how to do multiple switches instead of duplicating them 134 | // we need 135 | // send: low, medium, high, full 136 | // timer 1 (10 minutes), 2 (20), 3 (30) 137 | // To optimize testing, reset published state immediately so you can retrigger (i.e. momentarily button press) 138 | 139 | 140 | class FanSendLow : public Component, public Switch { 141 | public: 142 | 143 | void write_state(bool state) override { 144 | if (state) { 145 | noInterrupts(); 146 | rf.sendCommand(IthoLow); 147 | interrupts(); 148 | rf.initReceive(); 149 | State = 1; 150 | Timer = 0; 151 | LastID = Mydeviceid; 152 | publish_state(!state); 153 | } 154 | } 155 | }; 156 | 157 | class FanSendMedium : public Component, public Switch { 158 | public: 159 | 160 | void write_state(bool state) override { 161 | if ( state ) { 162 | noInterrupts(); 163 | rf.sendCommand(IthoMedium); 164 | interrupts(); 165 | rf.initReceive(); 166 | State = 2; 167 | Timer = 0; 168 | LastID = Mydeviceid; 169 | publish_state(!state); 170 | } 171 | } 172 | }; 173 | 174 | class FanSendHigh : public Component, public Switch { 175 | public: 176 | 177 | void write_state(bool state) override { 178 | if (state) { 179 | noInterrupts(); 180 | rf.sendCommand(IthoHigh); 181 | interrupts(); 182 | rf.initReceive(); 183 | State = 3; 184 | Timer = 0; 185 | LastID = Mydeviceid; 186 | publish_state(!state); 187 | } 188 | } 189 | }; 190 | 191 | class FanSendFull : public Component, public Switch { 192 | public: 193 | 194 | void write_state(bool state) override { 195 | if (state) { 196 | noInterrupts(); 197 | rf.sendCommand(IthoFull); 198 | interrupts(); 199 | rf.initReceive(); 200 | State = 4; 201 | Timer = 0; 202 | LastID = Mydeviceid; 203 | publish_state(!state); 204 | } 205 | } 206 | }; 207 | 208 | class FanSendIthoTimer1 : public Component, public Switch { 209 | public: 210 | 211 | void write_state(bool state) override { 212 | if ( state ) { 213 | noInterrupts(); 214 | rf.sendCommand(IthoTimer1); 215 | interrupts(); 216 | rf.initReceive(); 217 | State = 13; 218 | Timer = Time1; 219 | LastID = Mydeviceid; 220 | publish_state(!state); 221 | } 222 | } 223 | }; 224 | 225 | class FanSendIthoTimer2 : public Component, public Switch { 226 | public: 227 | 228 | void write_state(bool state) override { 229 | if ( state ) { 230 | noInterrupts(); 231 | rf.sendCommand(IthoTimer2); 232 | interrupts(); 233 | rf.initReceive(); 234 | State = 23; 235 | Timer = Time2; 236 | LastID = Mydeviceid; 237 | publish_state(!state); 238 | } 239 | } 240 | }; 241 | 242 | class FanSendIthoTimer3 : public Component, public Switch { 243 | public: 244 | 245 | void write_state(bool state) override { 246 | if ( state ) { 247 | noInterrupts(); 248 | rf.sendCommand(IthoTimer3); 249 | interrupts(); 250 | rf.initReceive(); 251 | State = 33; 252 | Timer = Time3; 253 | LastID = Mydeviceid; 254 | publish_state(!state); 255 | } 256 | } 257 | }; 258 | 259 | class FanSendIthoJoin : public Component, public Switch { 260 | public: 261 | 262 | void write_state(bool state) override { 263 | if ( state ) { 264 | noInterrupts(); 265 | rf.sendCommand(IthoJoin); 266 | interrupts(); 267 | rf.initReceive(); 268 | State = 1111; 269 | Timer = 0; 270 | publish_state(!state); 271 | } 272 | } 273 | }; 274 | 275 | 276 | void ITHOinterrupt() 277 | { 278 | // Signal ITHO received something 279 | ITHOhasPacket = true; 280 | } 281 | 282 | 283 | int RFRemoteIndex(String rfremoteid) 284 | { 285 | if (rfremoteid == Idlist[0].Id) return 0; 286 | else if (rfremoteid == Idlist[1].Id) return 1; 287 | else if (rfremoteid == Idlist[2].Id) return 2; 288 | else return -1; 289 | } 290 | 291 | 292 | void ITHOcheck() { 293 | noInterrupts(); // disable new interrupts 294 | if (rf.checkForNewPacket()) { 295 | IthoCommand cmd = rf.getLastCommand(); 296 | String Id = rf.getLastIDstr(); 297 | int index = RFRemoteIndex(Id); 298 | if ( index>=0) { // Only accept commands that are in the list 299 | switch (cmd) { 300 | case IthoUnknown: 301 | ESP_LOGV("custom", "Unknown command"); 302 | break; 303 | case IthoLow: 304 | case DucoLow: 305 | ESP_LOGD("custom", "IthoLow"); 306 | State = 1; 307 | Timer = 0; 308 | LastID = Idlist[index].Roomname; 309 | break; 310 | case IthoMedium: 311 | case DucoMedium: 312 | ESP_LOGD("custom", "Medium"); 313 | State = 2; 314 | Timer = 0; 315 | LastID = Idlist[index].Roomname; 316 | break; 317 | case IthoHigh: 318 | case DucoHigh: 319 | ESP_LOGD("custom", "High"); 320 | State = 3; 321 | Timer = 0; 322 | LastID = Idlist[index].Roomname; 323 | break; 324 | case IthoFull: 325 | ESP_LOGD("custom", "Full"); 326 | State = 4; 327 | Timer = 0; 328 | LastID = Idlist[index].Roomname; 329 | break; 330 | case IthoTimer1: 331 | ESP_LOGD("custom", "Timer1"); 332 | State = 13; 333 | Timer = Time1; 334 | LastID = Idlist[index].Roomname; 335 | break; 336 | case IthoTimer2: 337 | ESP_LOGD("custom", "Timer2"); 338 | State = 23; 339 | Timer = Time2; 340 | LastID = Idlist[index].Roomname; 341 | break; 342 | case IthoTimer3: 343 | ESP_LOGD("custom", "Timer3"); 344 | State = 33; 345 | Timer = Time3; 346 | LastID = Idlist[index].Roomname; 347 | break; 348 | case IthoJoin: 349 | break; 350 | case IthoLeave: 351 | break; 352 | default: 353 | break; 354 | } 355 | } 356 | else ESP_LOGV("","Ignored device-id: %s", Id.c_str()); 357 | } 358 | interrupts(); //enable interrupts 359 | } 360 | -------------------------------------------------------------------------------- /itho.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: fancontrol 3 | includes: 4 | - itho.h 5 | libraries: 6 | - SPI 7 | - https://github.com/jodur/ITHO-Lib#NewLib 8 | 9 | #Set ID from remotes that are used, so you can identify the root of the last State change 10 | on_boot: 11 | then: 12 | - lambda: |- 13 | Idlist[0]={"51,40,61","Badkamer"}; 14 | Idlist[1]={"73,82,11","Toilet"}; 15 | Idlist[2]={"ID3","ID3"}; 16 | Mydeviceid="Homeassistant"; 17 | rf.setDeviceID(10,87,81); //ID for sending: 10,87,81 was used in old library 18 | id(swfan_low).turn_on(); //This ensures fan is at low-speed at boot 19 | esp8266: 20 | board: d1_mini 21 | framework: 22 | version: recommended 23 | 24 | wifi: 25 | ssid: 26 | password: 27 | 28 | # Enable fallback hotspot (captive portal) in case wifi connection fails 29 | ap: 30 | ssid: "FALLBACK AP" 31 | password: "jdsh348201" 32 | 33 | captive_portal: 34 | 35 | # Enable logging 36 | logger: 37 | # level: VERBOSE #Enable this line to find out the ID of youre remote 38 | 39 | # Enable Home Assistant API 40 | api: 41 | 42 | ota: 43 | on_begin: 44 | then: 45 | - lambda: |- 46 | ESP_LOGI("custom", "Disable Interrupts, to allow OTA without intterupts"); 47 | detachInterrupt(D1); 48 | delay(500); 49 | 50 | switch: 51 | - platform: custom 52 | lambda: |- 53 | auto fansendlow = new FanSendLow(); 54 | App.register_component(fansendlow); 55 | return {fansendlow}; 56 | 57 | switches: 58 | name: "FanSendLow" 59 | id: swfan_low 60 | 61 | - platform: custom 62 | lambda: |- 63 | auto fansendmedium = new FanSendMedium(); 64 | App.register_component(fansendmedium); 65 | return {fansendmedium}; 66 | 67 | switches: 68 | name: "FanSendMedium" 69 | 70 | - platform: custom 71 | lambda: |- 72 | auto fansendhigh = new FanSendHigh(); 73 | App.register_component(fansendhigh); 74 | return {fansendhigh}; 75 | 76 | switches: 77 | name: "FanSendHigh" 78 | 79 | - platform: custom 80 | lambda: |- 81 | auto fansendt1 = new FanSendIthoTimer1(); 82 | App.register_component(fansendt1); 83 | return {fansendt1}; 84 | 85 | switches: 86 | name: "FanSendTimer1" 87 | 88 | - platform: custom 89 | lambda: |- 90 | auto fansendt2 = new FanSendIthoTimer2(); 91 | App.register_component(fansendt2); 92 | return {fansendt2}; 93 | 94 | switches: 95 | name: "FanSendTimer2" 96 | 97 | - platform: custom 98 | lambda: |- 99 | auto fansendt3 = new FanSendIthoTimer3(); 100 | App.register_component(fansendt3); 101 | return {fansendt3}; 102 | 103 | switches: 104 | name: "FanSendTimer3" 105 | 106 | - platform: custom 107 | lambda: |- 108 | auto fansendjoin = new FanSendIthoJoin(); 109 | App.register_component(fansendjoin); 110 | return {fansendjoin}; 111 | switches: 112 | name: "FanSendJoin" 113 | 114 | text_sensor: 115 | - platform: custom 116 | lambda: |- 117 | auto fanrecv = new FanRecv(); 118 | App.register_component(fanrecv); 119 | return {fanrecv->fanspeed,fanrecv->fantimer,fanrecv->Lastid}; 120 | text_sensors: 121 | - name: "fanspeed" 122 | icon: "mdi:transfer" 123 | - name: "fantimer" 124 | icon: "mdi:timer" 125 | - name: "lastid" 126 | 127 | 128 | 129 | 130 | 131 | --------------------------------------------------------------------------------