├── README.md ├── dist └── fan-mode-button-row.js ├── hacs.json ├── images └── fan-mode-button-row.jpg └── info.md /README.md: -------------------------------------------------------------------------------- 1 | # Fan Mode Button Row 2 | Provides a means to program 2 or 3 preset mode settings for fans selectable from a Lovelace button row. 3 | 4 | ## NOTE: You must be on Home Assistant V2021.3.X or higher to use this plug-in. However, if your fan integration hasn't been updated to the new control method (if it doesn't actually use preset modes) then you need to use the fan-control-entity-row. 5 | 6 | Installation: 7 | 8 | *** 9 | 10 | **NOTE:** 11 | 12 | For some reason there is a big backlog of requests to add repositories as defaulkt in HACS. Hopefully it gets resolved soon. 13 | 14 | In the meantime, you can still add this as a custom repository in HACS. 15 | 16 | Open HACS. Click on one of the sub-headings (integration, frontend, etc) then click the three dots in the top right of the HACS page and copy the URL for the main repo in there at the bottom. Select plugin as the type. It should show up in HACS to be able to be installed at that point. 17 | 18 | *** 19 | 20 | The easiest way to install this is to use the Home Assistant Community Store (HACS) in Home Assistant. 21 | 22 | Follow the instructions there for installation making sure you note the "url:" section for the resources addition. 23 | 24 | 25 | Conversely, if you don't use HACS you can install it manually by performing the following: 26 | 27 | Copy the fan-mode-button-row.js file to the appropriate folder in your Home Assistant Configuration directory (/config/www/). 28 | 29 | Place the following in your "resources" section in your lovelace configuration (updating the location to where you placed the above file): 30 | 31 | ``` 32 | - url: /local/fan-mode-button-row.js 33 | type: module 34 | ``` 35 | 36 | Then to use this in a card place the following in your entity card: 37 | 38 | 39 | Options: 40 | 41 | | Name | Type | Required | Default | Description | 42 | | --- | --- | --- | --- | --- | 43 | | entity | String | Yes | none | any fan entity_id | 44 | | type | String | Yes | none | custom:fan-mode-button-row | 45 | | customModes | Boolean | No | false | Set to true to use custom mode definitions | 46 | | modeOff | String | No | 'off' | Sets the mode for the 'OFF' button (see NOTE 2 below) | 47 | | modeOne | String | No | 'low' | Sets the mode for the 'Mode 1' (default 'LOW') button | 48 | | modeTwo | String | No | 'medium' | Sets the mode for the 'Mode 2' (default 'MED') button | 49 | | modeThree | String | No | 'high' | Sets the mode for the 'Mode 3' (default 'HIGH') button | 50 | | width | String | No | 30px | A custom width for the buttons | 51 | | height | String | No | 30px | A custom height for the buttons | 52 | | name | String | No | none | A custom name for the entity in the row | 53 | | customTheme | Boolean | No | false | set to true to use a custom theme | 54 | | reverseButtons | Boolean | No | false | set to true to reverse the button order | 55 | | hideOff | Boolean | No | false | Set to true to hide the Off button | 56 | | isOffColor | String | No | '#f44c09' | Sets the color of the 'OFF' button if fan is off | 57 | | isOnModeOneColor | String | No | '#43A047' | Sets the color of the 'Mode 1' (default 'LOW') button if fan is on Mode 1 | 58 | | isOnModeTwoColor | String | No | '#43A047' | Sets the color of the 'Mode 2' (default 'MED') button if fan is on Mode 2 | 59 | | isOnModeThreeColor | String | No | '#43A047' | Sets the color of the 'Mode 3' (default 'HIGH') button if fan is on Mode 3 | 60 | | buttonInactiveColor | String | No | '#759aaa' | Sets the color of the the buttons if that selection is not "active" | 61 | | customText | Boolean | No | false | Set to true to use custom text for buttons | 62 | | customOffText | String | No | 'OFF' | Sets the text of the 'OFF button | 63 | | customModeOneText | String | No | 'LOW' | Sets the text of the 'Mode 1' (default 'LOW') button | 64 | | customModeTwoText | String | No | 'MED' | Sets the text of the 'Mode 2' (default 'MED') button | 65 | | customModeThreeText | String | No | 'HIGH' | Sets the text of the "Mode 3" (default 'HIGH') button | 66 | | twoModeFan | Boolean | No | false | Set to true to hide the middle mode button ('modeTwo') for fans with only two modes | 67 | | sendStateWithMode | Boolean | No | false | Calls the turn_on service for the fan before sending the mode, used if fan does not power on automatically when mode is set | 68 | 69 | 70 | NOTES: 71 | 72 | 1. The default values for the service calls and button text are as noted above. If you need to use custom modes then you need to set the values of the mode buttons to exactly match the modes you want to control with this plug-in. 73 | 74 | 2. The "modeOff" button will ALWAYS turn the fan off no matter what the mode is set to for that button. However, if you use custom modes (customModes = 'true') the button text will change to display the mode name as set in the custom mode setting of the buttons. So, even though the "modeOff" button will only ever turn the fan off you may want to set a different mode for that button to display the desired text for the button. 75 | 76 | 3. If, however, you choose to use custom text (customText = 'true') the custom text settings for the button will override both the default names AND the custom mode names. 77 | 78 | Yes, it's a bit confusing but to summarize: 79 | 80 | - customModes = 'false' and customText = 'false' => default text as noted in the table above will be used 81 | 82 | - customModes = 'true' and customText = 'false' => text will be what is set for the mode for each button (Only true for modes changed from default above, otherwise they will display the defauilt mode text) 83 | 84 | - customText = 'true' (no matter what customModes is set to) => text will be what is set for each button by the custom text setting (Only true for text changed from default above otherwise they will display the defauilt text) 85 | 86 | That's the best I can explain it. Feel free to poke around at it and hopefully it will make more sense. 87 | 88 | The values for the colors can be any valid color string in "HEX", "RGB" or by color name. 89 | 90 | If the mode is changed via any other means (slider, service call, etc) the buttons will indicate which mode it is in. 91 | 92 | Configuration Examples: 93 | 94 | ```yaml 95 | type: entities 96 | title: Hall Fan Mode Preset Modes 97 | show_header_toggle: false 98 | entities: 99 | ## USE THIS CONFIG TO HAVE IT MATCH YOUR THEME ## 100 | - entity: fan.hall_fan 101 | type: custom:fan-mode-button-row 102 | name: Fan Not Custom Theme 103 | ## USE THIS CONFIG TO USE A DEFAULT CUSTOM THEME 104 | - entity: fan.hall_fan 105 | type: custom:fan-mode-button-row 106 | name: Fan Default Custom Theme 107 | customTheme: true 108 | ## USE THIS CONFIG TO USE A 'CUSTOMZED' CUSTOM THEME 109 | - entity: fan.hall_fan 110 | type: custom:fan-mode-button-row 111 | name: Fan Custom Custom Theme 112 | reverseButtons: true 113 | customTheme: true 114 | isOnMode1Color: 'rgb(255, 0, 0)' 115 | isOnMode2Color: '#888888' 116 | isOnMode3Color: '#222222' 117 | isOnMode4Color: 'purple' 118 | buttonInactiveColor: '#aaaaaa' 119 | ## FULL EXAMPLE CONFIGURATION 120 | - entity: fan.hall_fan 121 | type: custom:fan-mode-button-row 122 | name: Fan Custom Button Text 123 | twoModeFan: true 124 | reverseButtons: true 125 | customTheme: true 126 | isOnModeOneColor: 'rgb(255, 0, 0)' 127 | isOnModeTwoColor: '#888888' 128 | isOnModeThreeColor: '#222222' 129 | buttonInactiveColor: '#aaaaaa' 130 | isOffColor: 'purple' 131 | customModes: true 132 | modeOff: "brown" 133 | modeOne: "low" 134 | modeTwo: "medium" 135 | modeThree: "high" 136 | customText: true 137 | customOffText: 'NAY' 138 | customModeOneText: '1' 139 | customModeTwoText: 'mid' 140 | customModeThreeText: 'Fast' 141 | width: '15px' 142 | height: '15px' 143 | ``` 144 | 145 | Please see my fan packages in my Home-Assistant Repo for example configurations to use the above plugin configurations. 146 | 147 | https://github.com/finity69x2/Home-Assistant/tree/master/packages 148 | 149 | Examples of the above plugion configurations: 150 | 151 | ![FanModeExamples](images/fan-mode-button-row.jpg) 152 | 153 | -------------------------------------------------------------------------------- /dist/fan-mode-button-row.js: -------------------------------------------------------------------------------- 1 | window.customCards = window.customCards || []; 2 | window.customCards.push({ 3 | type: "fan-mode-button-row", 4 | name: "fan mode button row", 5 | description: "A plugin to display your fan controls in a button row.", 6 | preview: false, 7 | }); 8 | 9 | const LitElement = customElements.get("ha-panel-lovelace") ? Object.getPrototypeOf(customElements.get("ha-panel-lovelace")) : Object.getPrototypeOf(customElements.get("hc-lovelace")); 10 | const html = LitElement.prototype.html; 11 | const css = LitElement.prototype.css; 12 | 13 | 14 | class CustomFanModeRow extends LitElement { 15 | 16 | constructor() { 17 | super(); 18 | this._config = { 19 | customTheme: false, 20 | customSetpoints: false, 21 | reverseButtons: false, 22 | width: '30px', 23 | height: '30px', 24 | twoModeFan: false, 25 | hideOff: false, 26 | sendStateWithMode: false, 27 | allowDisablingButtons: true, 28 | customModes: false, 29 | customText: false, 30 | modeOff: "none", 31 | modeOne: "low", 32 | modeTwo: "medium", 33 | modeThree: "high", 34 | isOffColor: '#f44c09', 35 | isOnModeOneColor: '#43A047', 36 | isOnModeTwoColor: '#43A047', 37 | isOnModeThreeColor: '#43A047', 38 | buttonInactiveColor: '#759aaa', 39 | customOffText: 'OFF', 40 | customModeOneText: 'LOW', 41 | customModeTwoText: 'MED', 42 | customModeThreeText: 'HIGH', 43 | customOffText: 'OFF', 44 | customLowText: 'LOW', 45 | customMedText: 'MED', 46 | customHiText: 'HIGH', 47 | }; 48 | } 49 | 50 | static get properties() { 51 | return { 52 | hass: Object, 53 | _config: Object, 54 | _stateObj: Object, 55 | _modeOff: String, 56 | _modeOne: String, 57 | _modeTwo: String, 58 | _modeThree: String, 59 | _width: String, 60 | _height: String, 61 | _leftColor: String, 62 | _midLeftColor: String, 63 | _midRightColor: String, 64 | _rightColor: String, 65 | _leftText: String, 66 | _midLeftText: String, 67 | _midRightText: String, 68 | _rightText: String, 69 | _leftName: String, 70 | _midLeftName: String, 71 | _midRightName: String, 72 | _rightName: String, 73 | _leftState: Boolean, 74 | _midLeftState: Boolean, 75 | _midRightState: Boolean, 76 | _rightState: Boolean, 77 | _hideLeft: Boolean, 78 | _hideMidLeft: Boolean, 79 | _hideMidRight: Boolean, 80 | _hideRight: Boolean, 81 | }; 82 | } 83 | 84 | static get styles() { 85 | return css` 86 | :host { 87 | line-height: inherit; 88 | } 89 | .box { 90 | display: flex; 91 | flex-direction: row; 92 | } 93 | .mode { 94 | margin-left: 2px; 95 | margin-right: 2px; 96 | background-color: #759aaa; 97 | border: 1px solid lightgrey; 98 | border-radius: 4px; 99 | font-size: 10px !important; 100 | color: inherit; 101 | text-align: center; 102 | float: left !important; 103 | padding: 1px; 104 | cursor: pointer; 105 | } 106 | `; 107 | } 108 | 109 | render() { 110 | return html` 111 | 112 |
113 | 119 | 125 | 131 | 137 |
138 |
139 | `; 140 | } 141 | 142 | firstUpdated() { 143 | super.firstUpdated(); 144 | this.shadowRoot.getElementById('button-container').addEventListener('click', (ev) => ev.stopPropagation()); 145 | } 146 | 147 | setConfig(config) { 148 | this._config = { ...this._config, ...config }; 149 | } 150 | 151 | updated(changedProperties) { 152 | if (changedProperties.has("hass")) { 153 | this.hassChanged(); 154 | } 155 | } 156 | 157 | hassChanged(hass) { 158 | 159 | const config = this._config; 160 | const stateObj = this.hass.states[config.entity]; 161 | const custTheme = config.customTheme; 162 | const sendStateWithMode = config.sendStateWithMode; 163 | const revButtons = config.reverseButtons; 164 | const custModes = config.customModes; 165 | const custText = config.customText; 166 | const twoModes = config.twoModeFan; 167 | const hide_Off = config.hideOff; 168 | const buttonWidth = config.width; 169 | const buttonHeight = config.height; 170 | const onM1Clr = config.isOnModeOneColor; 171 | const onM2Clr = config.isOnModeTwoColor; 172 | const onM3Clr = config.isOnModeThreeColor; 173 | const offClr = config.isOffColor; 174 | const buttonOffClr = config.buttonInactiveColor; 175 | const mOff = config.modeOff; 176 | const m1 = config.modeOne; 177 | const m2 = config.modeTwo; 178 | const m3 = config.modeThree; 179 | const custOffTxt = config.customOffText; 180 | const custM1Txt = config.customModeOneText; 181 | const custM2Txt = config.customModeTwoText; 182 | const custM3Txt = config.customModeThreeText; 183 | 184 | let offstate; 185 | let mode1; 186 | let mode2; 187 | let mode3; 188 | 189 | if (custModes) { 190 | if (stateObj && stateObj.attributes) { 191 | if (stateObj.state == 'on' && stateObj.attributes.preset_mode == m1 ) { 192 | mode1 = 'on'; 193 | } else if (stateObj.state == 'on' && stateObj.attributes.preset_mode == m2 ) { 194 | mode2 = 'on'; 195 | } else if (stateObj.state == 'on' && stateObj.attributes.preset_mode == m3 ) { 196 | mode3 = 'on'; 197 | } else { 198 | offstate = 'on'; 199 | } 200 | } 201 | } else { 202 | if (stateObj && stateObj.attributes) { 203 | if (stateObj.state == 'on' && stateObj.attributes.preset_mode == "low" ) { 204 | mode1 = 'on'; 205 | } else if (stateObj.state == 'on' && stateObj.attributes.preset_mode == "medium" ) { 206 | mode2 = 'on'; 207 | } else if (stateObj.state == 'on' && stateObj.attributes.preset_mode == "high" ) { 208 | mode3 = 'on'; 209 | } else { 210 | offstate = 'on'; 211 | } 212 | } 213 | } 214 | 215 | let offtext; 216 | let m1text; 217 | let m2text; 218 | let m3text; 219 | 220 | if (custText) { 221 | offtext = custOffTxt; 222 | m1text = custM1Txt; 223 | m2text = custM2Txt; 224 | m3text = custM3Txt; 225 | } else if (custModes) { 226 | offtext = mOff; 227 | m1text = m1; 228 | m2text = m2; 229 | m3text = m3; 230 | } else { 231 | offtext = "OFF"; 232 | m1text = "LOW"; 233 | m2text = "MED"; 234 | m3text = "HIGH"; 235 | } 236 | 237 | let mode1color; 238 | let mode2color; 239 | let mode3color; 240 | let offcolor; 241 | 242 | 243 | if (custTheme) { 244 | if (mode1 == 'on') { 245 | mode1color = 'background-color:' + onM1Clr; 246 | } else { 247 | mode1color = 'background-color:' + buttonOffClr; 248 | } 249 | if (mode2 == 'on') { 250 | mode2color = 'background-color:' + onM2Clr; 251 | } else { 252 | mode2color = 'background-color:' + buttonOffClr; 253 | } 254 | if (mode3 == 'on') { 255 | mode3color = 'background-color:' + onM3Clr; 256 | } else { 257 | mode3color = 'background-color:' + buttonOffClr; 258 | } 259 | if (offstate == 'on') { 260 | offcolor = 'background-color:' + offClr; 261 | } else { 262 | offcolor = 'background-color:' + buttonOffClr; 263 | } 264 | } else { 265 | if (mode1 == 'on') { 266 | mode1color = 'background-color: var(--switch-checked-color)'; 267 | } else { 268 | mode1color = 'background-color: var(--switch-unchecked-color)'; 269 | } 270 | if (mode2 == 'on') { 271 | mode2color = 'background-color: var(--switch-checked-color)'; 272 | } else { 273 | mode2color = 'background-color: var(--switch-unchecked-color)'; 274 | } 275 | if (mode3 == 'on') { 276 | mode3color = 'background-color: var(--switch-checked-color)'; 277 | } else { 278 | mode3color = 'background-color: var(--switch-unchecked-color)'; 279 | } 280 | if (offstate == 'on') { 281 | offcolor = 'background-color: var(--switch-checked-color)'; 282 | } else { 283 | offcolor = 'background-color: var(--switch-unchecked-color)'; 284 | } 285 | } 286 | 287 | let hideoff = 'display:block'; 288 | let nohide = 'display:block'; 289 | 290 | if (hide_Off) { 291 | hideoff = 'display:none'; 292 | } else { 293 | hideoff = 'display:block'; 294 | } 295 | 296 | let twomodes_left; 297 | let twomodes_right; 298 | 299 | if (twoModes) { 300 | if (revButtons) { 301 | twomodes_right = 'display:none'; 302 | twomodes_left = 'display:block'; 303 | } else { 304 | twomodes_left = 'display:none'; 305 | twomodes_right = 'display:block'; 306 | } 307 | } else { 308 | twomodes_left = 'display:block'; 309 | twomodes_right = 'display:block'; 310 | } 311 | 312 | let buttonwidth = buttonWidth; 313 | let buttonheight = buttonHeight; 314 | 315 | let offname = 'off' 316 | let m1name = 'mode1' 317 | let m2name = 'mode2' 318 | let m3name = 'mode3' 319 | 320 | if (revButtons) { 321 | this._stateObj = stateObj; 322 | this._leftState = offstate == 'on'; 323 | this._midLeftState = mode1 === 'on'; 324 | this._midRightState = mode2 === 'on'; 325 | this._rightState = mode3 === 'on'; 326 | this._width = buttonwidth; 327 | this._height = buttonheight; 328 | this._leftColor = offcolor; 329 | this._midLeftColor = mode1color; 330 | this._midRightColor = mode2color; 331 | this._rightColor = mode3color; 332 | this._modeOff = mOff; 333 | this._modeOne = m1; 334 | this._modeTwo = m2; 335 | this._modeThree = m3; 336 | this._leftText = offtext; 337 | this._midLeftText = m1text; 338 | this._midRightText = m2text; 339 | this._rightText = m3text; 340 | this._leftName = offname; 341 | this._midLeftName = m1name; 342 | this._midRightName = m2name; 343 | this._rightName = m3name; 344 | this._hideLeft = hideoff; 345 | this._hideMidLeft = twomodes_left; 346 | this._hideMidRight = twomodes_right; 347 | this._hideRight = nohide; 348 | } else { 349 | this._stateObj = stateObj; 350 | this._leftState = mode3 == 'on'; 351 | this._midLeftState = mode2 === 'on'; 352 | this._midRightState = mode1 === 'on'; 353 | this._rightState = offstate === 'on'; 354 | this._width = buttonwidth; 355 | this._height = buttonheight; 356 | this._leftColor = mode3color; 357 | this._midLeftColor = mode2color; 358 | this._midRightColor = mode1color; 359 | this._rightColor = offcolor; 360 | this._modeOff = mOff; 361 | this._modeOne = m1; 362 | this._modeTwo = m2; 363 | this._modeThree = m3; 364 | this._leftText = m3text; 365 | this._midLeftText = m2text; 366 | this._midRightText = m1text; 367 | this._rightText = offtext; 368 | this._leftName = m3name; 369 | this._midLeftName = m2name; 370 | this._midRightName = m1name; 371 | this._rightName = offname; 372 | this._hideLeft = nohide; 373 | this._hideMidLeft = twomodes_left; 374 | this._hideMidRight = twomodes_right; 375 | this._hideRight = hideoff; 376 | } 377 | } 378 | 379 | setMode(e) { 380 | const mode = e.currentTarget.getAttribute('name'); 381 | const param = {entity_id: this._config.entity}; 382 | if(mode == 'off' ){ 383 | this.hass.callService('fan', 'turn_off', param); 384 | } else { 385 | if (this._config.sendStateWithMode) { 386 | this.hass.callService('fan', 'turn_on', param); 387 | } if (mode == 'mode1') { 388 | param.preset_mode = this._modeOne; 389 | this.hass.callService('fan', 'set_preset_mode', param); 390 | } else if (mode == 'mode2') { 391 | param.preset_mode = this._modeTwo; 392 | this.hass.callService('fan', 'set_preset_mode', param); 393 | } else if (mode == 'mode3') { 394 | param.preset_mode = this._modeThree; 395 | this.hass.callService('fan', 'set_preset_mode', param); 396 | } 397 | } 398 | } 399 | } 400 | 401 | customElements.define('fan-mode-button-row', CustomFanModeRow); 402 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fan Mode Button Row", 3 | "filename": "fan-mode-button-row.js" 4 | } 5 | -------------------------------------------------------------------------------- /images/fan-mode-button-row.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finity69x2/fan-mode-button-row/f79f647e9aee6c7fe9a808a9c0e98c48beabe519/images/fan-mode-button-row.jpg -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | Provides a means to program 3 preset mode settings for fans selectable from a Lovelace button row. 2 | 3 | ## NOTE: You must be on Home Assistant V2021.3.X or higher to use this plug-in 4 | 5 | Configuration Examples: 6 | 7 | ``` 8 | cards: 9 | - type: entities 10 | title: Hall Fan Mode Preset Modes 11 | show_header_toggle: false 12 | entities: 13 | ## USE THIS CONFIG TO HAVE IT MATCH YOUR THEME ## 14 | - entity: fan.hall_fan 15 | type: custom:fan-mode-button-row 16 | name: Fan Not Custom Theme 17 | ## USE THIS CONFIG TO USE A DEFAULT CUSTOM THEME 18 | - entity: fan.hall_fan 19 | type: custom:fan-mode-button-row 20 | name: Fan Default Custom Theme 21 | customTheme: true 22 | ## USE THIS CONFIG TO USE A 'CUSTOMZED' CUSTOM THEME 23 | - entity: fan.hall_fan 24 | type: custom:fan-mode-button-row 25 | name: Fan Custom Custom Theme 26 | reverseButtons: true 27 | customTheme: true 28 | isOnMode1Color: 'rgb(255, 0, 0)' 29 | isOnMode2Color: '#888888' 30 | isOnMode3Color: '#222222' 31 | isOnMode4Color: 'purple' 32 | buttonInactiveColor: '#aaaaaa' 33 | ## FULL EXAMPLE CONFIGURATION 34 | - entity: fan.hall_fan 35 | type: custom:fan-mode-button-row 36 | name: Fan Custom Button Text 37 | twoModeFan: true 38 | reverseButtons: true 39 | customTheme: true 40 | isOnModeOneColor: 'rgb(255, 0, 0)' 41 | isOnModeTwoColor: '#888888' 42 | isOnModeThreeColor: '#222222' 43 | buttonInactiveColor: '#aaaaaa' 44 | isOffColor: 'purple' 45 | customModes: true 46 | modeOff: "brown" 47 | modeOne: "low" 48 | modeTwo: "medium" 49 | modeThree: "high" 50 | customText: true 51 | customOffText: 'NAY' 52 | customModeOneText: '1' 53 | customModeTwoText: 'mid' 54 | customModeThreeText: 'Fast' 55 | width: '15px' 56 | height: '15px' 57 | 58 | ``` 59 | 60 | Please see my fan packages in my Home-Assistant Repo for example configurations to use the above plugin configurations. 61 | 62 | https://github.com/finity69x2/Home-Assistant/tree/master/packages 63 | 64 | Examples of the above plugion configurations: 65 | 66 | ![FanModeExamples](images/fan-mode-button-row.jpg) 67 | --------------------------------------------------------------------------------