├── README.md └── config └── esphome ├── itho.yaml └── ITHO └── fancontrol.h /README.md: -------------------------------------------------------------------------------- 1 | # Use the external components repository instead of this old archive 2 | 3 | :no_entry::warning::no_entry: Please update your bookmarks and install using https://github.com/CoMPaTech/esphome_ct/tree/main/components/cc1101 :no_entry::alert::no_entry: 4 | 5 | It features a proper fan unit and buttons to start the a timer or join 6 | 7 | # Controlling ITHO Mechanical Ventilation 8 | 9 | I've removed everything that wasn't working any more and provided a more clear way how to add this. 10 | 11 | For previous notes, please dive into the older commits :) 12 | 13 | ## Requirements 14 | 15 | - An ESP8266 (or compatible) 16 | - A C1101 board (including coil) 17 | - Some tinkering [see Mechanics](#mechanics) 18 | - Manually placing some files inside of Home Assistants `/config/esphome` directory 19 | 20 | 21 | ## How-to using Home Assistant 22 | 23 | Ensure you have the hardware ready ([Mechanics](#mechanics)) and the `esphome` add-on installed 24 | 25 | - Copy the directory structure of `config` into HA's `/config` (or manually ensure you have the dir `ITHO` and it contents present in `/config/esphome` and start configuring from the UI 26 | - See the sample contents in `/config/esphome/itho.yaml` for some idea of what is neccesary (obviously needs keys and secrets changed) 27 | - Build and install on the ESP of you choice from Home Assistant 28 | - Go into HA, choose `integrations` and add your ITHO (it should auto-discover it). 29 | - If everything goes well, you'll see `switch.fansend...` and quiet a few others popping up. 30 | - Enjoy! 31 | 32 | 33 | ## Current status: 34 | 35 | - Sending High/Medium/Low and the Timers work 36 | - Reading state and indicative time (not counting down (yet) - should HA or ESPhome do that?) 37 | 38 | ## ESPHome ITHO control 39 | Trying to get ESPHome to mimic what is comprised in 40 | 41 | - https://github.com/jodur/ESPEASY_Plugin_ITHO/blob/master/_P145_Itho.ino 42 | - https://github.com/adri/IthoEcoFanRFT / https://github.com/supersjimmie/IthoEcoFanRFT 43 | 44 | 45 | # Mechanics 46 | 47 | ## Wiring schema used 48 | 49 | ``` 50 | Connections between the CC1101 and the ESP8266 or Arduino: 51 | CC11xx pins ESP pins Arduino pins Description 52 | * 1 - VCC VCC VCC 3v3 53 | * 2 - GND GND GND Ground 54 | * 3 - MOSI 13=D7 Pin 11 Data input to CC11xx 55 | * 4 - SCK 14=D5 Pin 13 Clock pin 56 | * 5 - MISO/GDO1 12=D6 Pin 12 Data output from CC11xx / serial clock from CC11xx 57 | * 6 - GDO2 04=D2 Pin 2 Programmable output 58 | * 7 - GDO0 ? Pin ? Programmable output 59 | * 8 - CSN 15=D8 Pin 10 Chip select / (SPI_SS) 60 | ``` 61 | 62 | # Dependencies 63 | 64 | - Home Assistant 65 | - ESPHome 66 | - https://github.com/CoMPaTech/esphome_itho 67 | 68 | -------------------------------------------------------------------------------- /config/esphome/itho.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: fancontrol 3 | platform: ESP8266 4 | board: d1_mini_pro 5 | includes: 6 | - ITHO/fancontrol.h 7 | libraries: 8 | - SPI 9 | - https://github.com/CoMPaTech/esphome_itho.git 10 | - Ticker 11 | 12 | 13 | wifi: 14 | ssid: "secret" 15 | password: "evenmoresecret" 16 | 17 | # Enable logging 18 | logger: 19 | 20 | # Enable Home Assistant API 21 | api: 22 | encryption: 23 | key: 'somethinggenerated' 24 | 25 | ota: 26 | password: 'fan' 27 | 28 | fan: 29 | - platform: speed 30 | speed_count: 3 31 | id: mech_vent_fan 32 | output: mech_vent 33 | name: "Mechanische ventilatie" 34 | restore_mode: NO_RESTORE 35 | on_turn_on: 36 | - logger.log: "Fan Turned On, setting to 10min!" 37 | - switch.turn_on: fansendt1 38 | 39 | on_turn_off: 40 | - logger.log: "Fan Turned Off, setting to low speed!" 41 | - switch.turn_on: fansendlow 42 | 43 | 44 | output: 45 | - platform: template 46 | id: mech_vent 47 | type: float 48 | write_action: 49 | - if: 50 | condition: 51 | lambda: return ((state == 0)); 52 | then: 53 | # action for off 54 | - logger.log: "Fan set to low speed" 55 | - switch.turn_on: fansendlow 56 | - if: 57 | condition: 58 | lambda: return ((state > 0) && (state < .34)); 59 | then: 60 | # action for low 61 | - logger.log: "Fan set to low speed" 62 | - switch.turn_on: fansendlow 63 | - if: 64 | condition: 65 | lambda: return ((state > .34) && (state < .67)); 66 | then: 67 | # action for medium 68 | - logger.log: "Fan set to medium speed" 69 | - switch.turn_on: fansendmedium 70 | 71 | - if: 72 | condition: 73 | lambda: return ((state == 1)); 74 | then: 75 | # action for high 76 | - logger.log: "Fan set to high speed" 77 | - switch.turn_on: fansendhigh 78 | 79 | 80 | switch: 81 | - platform: custom 82 | lambda: |- 83 | id(mech_vent).set_level(0.33); 84 | auto fansendlow = new FanSendLow(); 85 | App.register_component(fansendlow); 86 | return {fansendlow}; 87 | 88 | switches: 89 | name: "FanSendLow" 90 | id: fansendlow 91 | 92 | - platform: custom 93 | lambda: |- 94 | id(mech_vent).set_level(0.66); 95 | auto fansendmedium = new FanSendMedium(); 96 | App.register_component(fansendmedium); 97 | return {fansendmedium}; 98 | 99 | switches: 100 | name: "FanSendMedium" 101 | id: fansendmedium 102 | 103 | - platform: custom 104 | lambda: |- 105 | id(mech_vent).set_level(1); 106 | auto fansendhigh = new FanSendHigh(); 107 | App.register_component(fansendhigh); 108 | return {fansendhigh}; 109 | 110 | switches: 111 | name: "FanSendHigh" 112 | id: fansendhigh 113 | 114 | - platform: custom 115 | lambda: |- 116 | id(mech_vent).set_level(1); 117 | auto fansendt1 = new FanSendIthoTimer1(); 118 | App.register_component(fansendt1); 119 | return {fansendt1}; 120 | 121 | switches: 122 | name: "FanSendTimer1" 123 | id: fansendt1 124 | 125 | - platform: custom 126 | lambda: |- 127 | id(mech_vent).set_level(1); 128 | auto fansendt2 = new FanSendIthoTimer2(); 129 | App.register_component(fansendt2); 130 | return {fansendt2}; 131 | 132 | switches: 133 | name: "FanSendTimer2" 134 | 135 | - platform: custom 136 | lambda: |- 137 | id(mech_vent).set_level(1); 138 | auto fansendt3 = new FanSendIthoTimer3(); 139 | App.register_component(fansendt3); 140 | return {fansendt3}; 141 | 142 | switches: 143 | name: "FanSendTimer3" 144 | 145 | - platform: custom 146 | lambda: |- 147 | auto fansendjoin = new FanSendIthoJoin(); 148 | App.register_component(fansendjoin); 149 | return {fansendjoin}; 150 | switches: 151 | name: "FanSendJoin" 152 | 153 | text_sensor: 154 | - platform: custom 155 | lambda: |- 156 | auto fanrecv = new FanRecv(); 157 | App.register_component(fanrecv); 158 | return {fanrecv->fanspeed,fanrecv->fantimer,fanrecv->remoteid1,fanrecv->remoteid2,fanrecv->lastid}; 159 | 160 | text_sensors: 161 | - name: "FanSpeed" 162 | on_value: 163 | then: 164 | lambda: |- 165 | ESP_LOGD("main", "The current version is %s", x.c_str()); 166 | if (strcmp(x.c_str(),"Low")==0) { 167 | // id(mech_vent).set_level(0.33); 168 | // id(mech_vent).set_level(0); 169 | ESP_LOGD("main", "Updating speed to low - mocked"); 170 | } 171 | if (strcmp(x.c_str(),"Medium")==0) { 172 | // id(mech_vent).set_level(0.67); 173 | // id(mech_vent).set_level(2); 174 | ESP_LOGD("main", "Updating speed medium - mocked"); 175 | } 176 | if (strcmp(x.c_str(),"High")==0) { 177 | // id(mech_vent).set_level(3); 178 | ESP_LOGD("main", "Updating speed high - mocked"); 179 | } 180 | - name: "FanTimer" 181 | - name: "RemoteID1" 182 | - name: "RemoteID2" 183 | - name: "LastID" 184 | 185 | 186 | sensor: 187 | - platform: wifi_signal 188 | name: "FanControl Signal" 189 | update_interval: 60s 190 | - platform: uptime 191 | name: "FanControl Uptime" 192 | 193 | -------------------------------------------------------------------------------- /config/esphome/ITHO/fancontrol.h: -------------------------------------------------------------------------------- 1 | #include "esphome.h" 2 | #include "IthoCC1101.h" 3 | #include "Ticker.h" 4 | 5 | IthoCC1101 rf; 6 | void ITHOinterrupt() IRAM_ATTR; 7 | void ITHOcheck(); 8 | 9 | // extra for interrupt handling 10 | bool ITHOhasPacket = false; 11 | Ticker ITHOticker; 12 | String State="Low"; // after startup it is assumed that the fan is running low 13 | String OldState="Low"; 14 | int Timer=0; 15 | int LastIDindex = 0; 16 | int OldLastIDindex = 0; 17 | long LastPublish=0; 18 | bool InitRunned = false; 19 | IthoPacket pkt; 20 | String LastID = ""; 21 | 22 | // Timer values for hardware timer in Fan 23 | #define Time1 10*60 24 | #define Time2 20*60 25 | #define Time3 30*60 26 | 27 | 28 | class FanRecv : public PollingComponent { 29 | public: 30 | 31 | TextSensor *fanspeed = new TextSensor(); 32 | TextSensor *fantimer = new TextSensor(); 33 | TextSensor *remoteid1 = new TextSensor(); 34 | TextSensor *remoteid2 = new TextSensor(); 35 | TextSensor *lastid = new TextSensor(); 36 | 37 | FanRecv() : PollingComponent(15000) { } 38 | 39 | void setup() { 40 | rf.init(); 41 | pinMode(D1, INPUT); 42 | attachInterrupt(D1, ITHOinterrupt, RISING); 43 | //attachInterrupt(D1, ITHOcheck, RISING); 44 | rf.initReceive(); 45 | } 46 | 47 | String converter(uint8_t *str){ 48 | return String((char *)str); 49 | } 50 | 51 | void update() override { 52 | fanspeed->publish_state(State.c_str()); 53 | fantimer->publish_state(String(Timer).c_str()); 54 | remoteid1->publish_state(converter(pkt.deviceId1).c_str()); 55 | remoteid2->publish_state(converter(pkt.deviceId2).c_str()); 56 | lastid->publish_state(LastID.c_str()); 57 | } 58 | 59 | 60 | }; 61 | 62 | class FanSendFull : public Component, public Switch { 63 | public: 64 | 65 | void write_state(bool state) override { 66 | if ( state ) { 67 | rf.sendCommand(IthoFull); 68 | State = "High"; 69 | Timer = 0; 70 | publish_state(!state); 71 | } 72 | } 73 | }; 74 | 75 | class FanSendHigh : public Component, public Switch { 76 | public: 77 | 78 | void write_state(bool state) override { 79 | if ( state ) { 80 | rf.sendCommand(IthoHigh); 81 | State = "High"; 82 | Timer = 0; 83 | publish_state(!state); 84 | } 85 | } 86 | }; 87 | 88 | class FanSendMedium : public Component, public Switch { 89 | public: 90 | 91 | void write_state(bool state) override { 92 | if ( state ) { 93 | rf.sendCommand(IthoMedium); 94 | State = "Medium"; 95 | Timer = 0; 96 | publish_state(!state); 97 | } 98 | } 99 | }; 100 | 101 | class FanSendLow : public Component, public Switch { 102 | public: 103 | 104 | void write_state(bool state) override { 105 | if ( state ) { 106 | rf.sendCommand(IthoLow); 107 | State = "Low"; 108 | Timer = 0; 109 | publish_state(!state); 110 | } 111 | } 112 | }; 113 | 114 | class FanSendIthoTimer1 : public Component, public Switch { 115 | public: 116 | 117 | void write_state(bool state) override { 118 | if ( state ) { 119 | rf.sendCommand(IthoTimer1); 120 | State = "High"; 121 | Timer = Time1; 122 | publish_state(!state); 123 | } 124 | } 125 | }; 126 | 127 | class FanSendIthoTimer2 : public Component, public Switch { 128 | public: 129 | 130 | void write_state(bool state) override { 131 | if ( state ) { 132 | rf.sendCommand(IthoTimer2); 133 | State = "High"; 134 | Timer = Time2; 135 | publish_state(!state); 136 | } 137 | } 138 | }; 139 | 140 | class FanSendIthoTimer3 : public Component, public Switch { 141 | public: 142 | 143 | void write_state(bool state) override { 144 | if ( state ) { 145 | rf.sendCommand(IthoTimer3); 146 | State = "High"; 147 | Timer = Time3; 148 | publish_state(!state); 149 | } 150 | } 151 | }; 152 | 153 | class FanSendIthoJoin : public Component, public Switch { 154 | public: 155 | 156 | void write_state(bool state) override { 157 | if ( state ) { 158 | rf.sendCommand(IthoJoin); 159 | State = "Join"; 160 | Timer = 0; 161 | publish_state(!state); 162 | } 163 | } 164 | }; 165 | 166 | void ITHOinterrupt() { 167 | ITHOticker.once_ms(10, ITHOcheck); 168 | } 169 | 170 | void ITHOcheck() { 171 | noInterrupts(); 172 | if (rf.checkForNewPacket()) { 173 | IthoCommand cmd = rf.getLastCommand(); 174 | IthoPacket pkt = rf.getLastPacket(); 175 | LastID = rf.getLastIDstr(); 176 | switch (cmd) { 177 | case IthoUnknown: 178 | //ESP_LOGD("custom", "Unknown state, packet is"); 179 | break; 180 | case IthoLow: 181 | ESP_LOGD("custom", "Low"); 182 | State = "Low"; 183 | Timer = 0; 184 | break; 185 | case IthoMedium: 186 | ESP_LOGD("custom", "Medium"); 187 | State = "Medium"; 188 | Timer = 0; 189 | break; 190 | case IthoHigh: 191 | ESP_LOGD("custom", "High"); 192 | State = "High"; 193 | Timer = 0; 194 | break; 195 | case IthoFull: 196 | ESP_LOGD("custom", "Full"); 197 | State = "Full"; 198 | Timer = 0; 199 | break; 200 | case IthoTimer1: 201 | ESP_LOGD("custom", "Timer1"); 202 | State = "High"; 203 | Timer = Time1; 204 | break; 205 | case IthoTimer2: 206 | ESP_LOGD("custom", "Timer2"); 207 | State = "High"; 208 | Timer = Time2; 209 | break; 210 | case IthoTimer3: 211 | ESP_LOGD("custom", "Timer3"); 212 | State = "High 30"; 213 | Timer = Time3; 214 | break; 215 | case IthoJoin: 216 | break; 217 | case IthoLeave: 218 | break; 219 | default: 220 | break; 221 | } 222 | } 223 | interrupts(); 224 | } 225 | --------------------------------------------------------------------------------