├── .gitignore ├── LICENSE ├── README.md ├── README_TW.md ├── Screenshot ├── fan.png └── fanui.png ├── hacs.json └── python_scripts └── fan_speed_control.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 iml885203 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HA-FanSpeedControl 2 | 3 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) 4 | [![Donate](https://img.shields.io/badge/donate-Coffee-yellow.svg)](https://www.buymeacoffee.com/dodoro) 5 | 6 | A python script for Home Assistant that control fan speed with [Fan Template](https://www.home-assistant.io/integrations/fan.template/) and [Broadlink](https://www.home-assistant.io/integrations/broadlink/). 7 | 8 | # Document 9 | 10 | - [Chinese Document](https://github.com/iml885203/HA-FanSpeedControl/blob/master/README_TW.md) 11 | - [English Document](https://github.com/iml885203/HA-FanSpeedControl/blob/master/README.md) 12 | 13 | # How it work 14 | 15 | The script automatically call broadlink service when you set fan speed. 16 | 17 | ## Example 18 | 19 | if your fan speed range is 1~12. 20 | 21 | example1: call `increase` fan 4 times when you set fan speed from 1 to 5. 22 | 23 | example2: call `decrease` fan 3 times when you set fan speed from 5 to 2. 24 | 25 | example3: call `decrease` fan 2 times when you set fan speed from 2 to 12. 26 | 27 | # Installation 28 | 29 | enable [python_script](https://www.home-assistant.io/integrations/python_script/) for your HomeAssistant. 30 | 31 | - Add to `configuration.yaml`: `python_script:` 32 | - Create folder `/python_scripts` 33 | - restart HomeAssistant 34 | 35 | Find `Fan Speed Control` on HACS automation category. 36 | 37 | Or you can copy the Python script in to your `/python_scripts` directory. 38 | 39 | # Script arguments 40 | 41 | |key|required|type|description| 42 | |-|-|-|-| 43 | |fan_speed|true|string|speed from fan template| 44 | |fan_speed_entity_id|true|string|| 45 | |fan_entity_id|true|string|| 46 | |fan_speed_count|true|integer|| 47 | |service_domain|true|string|| 48 | |service|true|string|| 49 | |service_data_increase|true|object|| 50 | |service_data_decrease|true|object|| 51 | 52 | # Config Example 53 | 54 | `set_percentage` on template fan 55 | 56 | ```yaml 57 | set_percentage: 58 | - service: python_script.fan_speed_control 59 | data_template: 60 | fan_speed: "{{ percentage }}" 61 | fan_speed_entity_id: 'input_number.status_fan_speed' 62 | fan_entity_id: 'fan.bedroom_fan' 63 | fan_speed_count: 12 64 | service_domain: 'remote' 65 | service: 'send_command' 66 | service_data_increase: 67 | entity_id: remote.broadlink 68 | device: fan 69 | command: increase 70 | service_data_decrease: 71 | entity_id: remote.broadlink 72 | device: fan 73 | command: decrease 74 | ``` 75 | 76 | ## Template Fan config 77 | 78 | ```yaml 79 | input_boolean: 80 | status_fan_power: 81 | name: 'Fan Power' 82 | 83 | input_number: 84 | status_fan_speed: 85 | name: 'Fan Speed' 86 | min: 0 87 | max: 100 88 | 89 | input_select: 90 | fan_osc: 91 | name: 'Fan osc' 92 | options: 93 | - 'True' 94 | - 'False' 95 | fan: 96 | - platform: template 97 | fans: 98 | bedroom_fan: 99 | friendly_name: "myFan" 100 | speed_count: 12 101 | value_template: "{{ states('input_boolean.status_fan_power') }}" 102 | percentage_template: "{{ states('input_number.status_fan_speed') | int }}" 103 | oscillating_template: "{{ states('input_select.fan_osc') }}" 104 | turn_on: 105 | - condition: state 106 | entity_id: input_boolean.status_fan_power 107 | state: 'off' 108 | - service: remote.send_command 109 | data: 110 | entity_id: remote.broadlink 111 | device: fan 112 | command: toggle 113 | - service: input_boolean.turn_on 114 | entity_id: input_boolean.status_fan_power 115 | turn_off: 116 | - condition: state 117 | entity_id: input_boolean.status_fan_power 118 | state: 'on' 119 | - service: remote.send_command 120 | data: 121 | entity_id: remote.broadlink 122 | device: fan 123 | command: toggle 124 | - service: input_boolean.turn_off 125 | entity_id: input_boolean.status_fan_power 126 | set_percentage: 127 | - service: python_script.fan_speed_control 128 | data_template: 129 | fan_speed: "{{ percentage }}" 130 | fan_speed_entity_id: 'input_number.status_fan_speed' 131 | fan_entity_id: 'fan.bedroom_fan' 132 | fan_speed_count: 12 133 | service_domain: 'remote' 134 | service: 'send_command' 135 | service_data_increase: 136 | entity_id: remote.broadlink 137 | device: fan 138 | command: increase 139 | service_data_decrease: 140 | entity_id: remote.broadlink 141 | device: fan 142 | command: decrease 143 | set_oscillating: 144 | - condition: state 145 | entity_id: input_boolean.status_fan_power 146 | state: 'on' 147 | - service: remote.send_command 148 | data: 149 | entity_id: remote.broadlink 150 | device: fan 151 | command: oscillate 152 | - service: input_select.select_next 153 | entity_id: input_select.fan_osc 154 | ``` 155 | 156 | # Debug 157 | 158 | add logger to your `configuration.yaml` 159 | 160 | ```yaml 161 | logger: 162 | default: warn 163 | logs: 164 | homeassistant.components.python_script.fan_speed_control.py: debug 165 | ``` 166 | 167 | # Screenshot 168 | 169 | ![image](https://github.com/iml885203/HA-FanSpeedControl/blob/master/Screenshot/fan.png?raw=true) 170 | 171 | ## Custom ui 172 | 173 | ![image](https://github.com/iml885203/HA-FanSpeedControl/blob/master/Screenshot/fanui.png?raw=true) 174 | 175 | # Todos 176 | 177 | - [ ] Refactor arguments, like: remove not used argument `service` 178 | - [ ] Find some way to get `entity.state` and `fan speed` not by arguments 179 | 180 |

181 |

182 |
183 | 184 | 185 | 186 |

187 | -------------------------------------------------------------------------------- /README_TW.md: -------------------------------------------------------------------------------- 1 | # HA-FanSpeedControl 2 | 3 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) 4 | [![Donate](https://img.shields.io/badge/donate-Coffee-yellow.svg)](https://www.buymeacoffee.com/dodoro) 5 | 6 | 用於Home Assistant的Python腳本,使用[Fan Template](https://www.home-assistant.io/integrations/fan.template/)和[Broadlink](https://www.home-assistant.io/integrations/broadlink/)控制風扇速度。 7 | 8 | # 說明 9 | 10 | - [中文說明](https://github.com/iml885203/HA-FanSpeedControl/blob/master/README_TW.md) 11 | - [英文說明](https://github.com/iml885203/HA-FanSpeedControl/blob/master/README.md) 12 | 13 | # 如何運作 14 | 15 | 當你設定風速時,腳本會自動呼叫多次broadlink service 16 | 17 | ## 範例 18 | 19 | 如果你的風速範圍為1~12 20 | 21 | 範例1: 當你設定風速從1到5時,會呼叫 `增加` 風速4次 22 | 23 | 範例2: 當你設定風速從5到2時,會呼叫 `減少` 風速3次 24 | 25 | 範例3: 當你設定風速從2到12時,會呼叫 `減少` 風速2次 26 | 27 | # 安裝 28 | 29 | 在 HomeAssistant 啟用 [python_script](https://www.home-assistant.io/integrations/python_script/) 30 | 31 | - 增加`python_script:`到你的`configuration.yaml` 32 | - 創建資料夾 `/python_scripts` 33 | - 重新啟動 HomeAssistant 34 | 35 | 在HACS的automation分類裡面找到`Fan Speed Control`安裝 36 | 37 | 或是直接複製Python腳本到你的`/python_scripts`資料夾裡面 38 | 39 | # 參數 40 | 41 | |key|required|type|description| 42 | |-|-|-|-| 43 | |fan_speed|true|string|speed from fan template| 44 | |fan_speed_entity_id|true|string|| 45 | |fan_entity_id|true|string|| 46 | |fan_speed_count|true|integer|| 47 | |service_domain|true|string|| 48 | |service|true|string|| 49 | |service_data_increase|true|object|| 50 | |service_data_decrease|true|object|| 51 | 52 | # 設定範例 53 | 54 | 設定在[Fan template](https://www.home-assistant.io/integrations/fan.template/)的 `set_percentage` 上 55 | 56 | ```yaml 57 | set_percentage: 58 | - service: python_script.fan_speed_control 59 | data_template: 60 | fan_speed: "{{ percentage }}" 61 | fan_speed_entity_id: 'input_number.status_fan_speed' 62 | fan_entity_id: 'fan.bedroom_fan' 63 | fan_speed_count: 12 64 | service_domain: 'remote' 65 | service: 'send_command' 66 | service_data_increase: 67 | entity_id: remote.broadlink 68 | device: fan 69 | command: increase 70 | service_data_decrease: 71 | entity_id: remote.broadlink 72 | device: fan 73 | command: decrease 74 | ``` 75 | 76 | ## 完整範例 77 | 78 | ```yaml 79 | input_boolean: 80 | status_fan_power: 81 | name: 'Fan Power' 82 | 83 | input_number: 84 | status_fan_speed: 85 | name: 'Fan Speed' 86 | max: 0 87 | max: 100 88 | 89 | input_select: 90 | fan_osc: 91 | name: 'Fan osc' 92 | options: 93 | - 'True' 94 | - 'False' 95 | fan: 96 | - platform: template 97 | fans: 98 | bedroom_fan: 99 | friendly_name: "myFan" 100 | speed_count: 12 101 | value_template: "{{ states('input_boolean.status_fan_power') }}" 102 | percentage_template: "{{ states('input_number.status_fan_speed') | int }}" 103 | oscillating_template: "{{ states('input_select.fan_osc') }}" 104 | turn_on: 105 | - condition: state 106 | entity_id: input_boolean.status_fan_power 107 | state: 'off' 108 | - service: remote.send_command 109 | data: 110 | entity_id: remote.broadlink 111 | device: fan 112 | command: toggle 113 | - service: input_boolean.turn_on 114 | entity_id: input_boolean.status_fan_power 115 | turn_off: 116 | - condition: state 117 | entity_id: input_boolean.status_fan_power 118 | state: 'on' 119 | - service: remote.send_command 120 | data: 121 | entity_id: remote.broadlink 122 | device: fan 123 | command: toggle 124 | - service: input_boolean.turn_off 125 | entity_id: input_boolean.status_fan_power 126 | set_percentage: 127 | - service: python_script.fan_speed_control 128 | data_template: 129 | fan_speed: "{{ percentage }}" 130 | fan_speed_entity_id: 'input_number.status_fan_speed' 131 | fan_entity_id: 'fan.bedroom_fan' 132 | fan_speed_count: 12 133 | service_domain: 'remote' 134 | service: 'send_command' 135 | service_data_increase: 136 | entity_id: remote.broadlink 137 | device: fan 138 | command: increase 139 | service_data_decrease: 140 | entity_id: remote.broadlink 141 | device: fan 142 | command: decrease 143 | set_oscillating: 144 | - condition: state 145 | entity_id: input_boolean.status_fan_power 146 | state: 'on' 147 | - service: remote.send_command 148 | data: 149 | entity_id: remote.broadlink 150 | device: fan 151 | command: oscillate 152 | - service: input_select.select_next 153 | entity_id: input_select.fan_osc 154 | ``` 155 | 156 | # 除錯 157 | 158 | 增加logger到 `configuration.yaml` 159 | 160 | ```yaml 161 | logger: 162 | default: warn 163 | logs: 164 | homeassistant.components.python_script.fan_speed_control.py: debug 165 | ``` 166 | 167 | # 截圖 168 | 169 | ![image](https://github.com/iml885203/HA-FanSpeedControl/blob/master/Screenshot/fan.png?raw=true) 170 | 171 | ## 客製化UI 172 | 173 | ![image](https://github.com/iml885203/HA-FanSpeedControl/blob/master/Screenshot/fanui.png?raw=true) 174 | 175 |

176 |

177 |
178 | 179 | 180 | 181 |

182 | -------------------------------------------------------------------------------- /Screenshot/fan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iml885203/HA-FanSpeedControl/5620b54386be14622e0d0f72cde6a515ad1a8954/Screenshot/fan.png -------------------------------------------------------------------------------- /Screenshot/fanui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iml885203/HA-FanSpeedControl/5620b54386be14622e0d0f72cde6a515ad1a8954/Screenshot/fanui.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fan Speed Control", 3 | "render_readme": "true" 4 | } 5 | -------------------------------------------------------------------------------- /python_scripts/fan_speed_control.py: -------------------------------------------------------------------------------- 1 | #### Init 2 | service_domain = data.get('service_domain') 3 | service = data.get('service') 4 | service_data_increase = data.get('service_data_increase') 5 | service_data_decrease = data.get('service_data_decrease') 6 | # fan speed data 7 | speed = data.get('fan_speed') 8 | speed_count = data.get('fan_speed_count') 9 | fan_speed_entity = hass.states.get(data.get('fan_speed_entity_id')) 10 | fan_entity = hass.states.get(data.get('fan_entity_id')) 11 | 12 | logger.debug(' fan state ({})'.format(fan_entity.state)) 13 | logger.debug(' Received fan speed from ({}) to ({})'.format(fan_speed_entity.state, speed)) 14 | 15 | ### def 16 | def check_speed(logger, speed): 17 | if speed is None: 18 | logger.warning(' Received fan speed is invalid (None)') 19 | return False 20 | 21 | if fan_entity.state is 'off': 22 | logger.warning(' can not change speed when fan is off') 23 | return False 24 | 25 | return True 26 | 27 | 28 | ### Run 29 | if check_speed(logger, speed): 30 | speed_step = 100 // speed_count 31 | target_speed = int(speed) // speed_step 32 | last_speed = int(float(fan_speed_entity.state)) // speed_step if fan_speed_entity.state else 1 33 | speed_max = speed_count 34 | 35 | if target_speed > last_speed: 36 | increase_loop = target_speed - last_speed 37 | decrease_loop = last_speed + speed_max - target_speed 38 | else: 39 | increase_loop = target_speed + speed_max - last_speed 40 | decrease_loop = last_speed - target_speed 41 | 42 | # check use increase or decrease 43 | if decrease_loop < increase_loop: 44 | loop = decrease_loop 45 | service_data = service_data_decrease 46 | else: 47 | loop = increase_loop 48 | service_data = service_data_increase 49 | 50 | # update speed state 51 | hass.services.call('input_number', 'set_value', { 52 | 'entity_id': data.get('fan_speed_entity_id'), 53 | 'value': speed 54 | }) 55 | 56 | # Call service 57 | if data.get('support_num_repeats', False): 58 | service_data['num_repeats'] = loop 59 | logger.debug(' call service ({}.{}) {}'.format(service_domain, service, service_data)) 60 | hass.services.call(service_domain, service, service_data) 61 | else: 62 | for i in range(loop): 63 | logger.debug(' call service ({}.{}) {}'.format(service_domain, service, service_data)) 64 | result = hass.services.call(service_domain, service, service_data) 65 | time.sleep(0.75) 66 | 67 | 68 | elif fan_entity.state is not 'off' and speed == 'off': 69 | logger.debug(' call fan off') 70 | hass.services.call('fan', 'turn_off', { 71 | 'entity_id': data.get('fan_entity_id') 72 | }) 73 | --------------------------------------------------------------------------------