Skip to content

Commit

Permalink
Fixes for leshow.fan.ss4 support (#58)
Browse files Browse the repository at this point in the history
* Added Sleep mode button (always visible for ss4 fan, for other fan can be enabled)
* Added ability to use HA standard modes (low / medium / high)
* Fixes for ss4 fan support
* Fixes due to sleep and natural mode separation
  • Loading branch information
insajd authored May 12, 2021
1 parent 58c2d92 commit 97a4022
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 30 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ Xiaomi Smartmi Fan Lovelace card for HASS/Home Assistant.
## Features
- Supports [HACS](https://github.com/custom-components/hacs) installation
- CSS fan blade and oscillation animation
- UI config of card
- Wide range of fans support by using HA standard speeds flag

### Toggles
- Oscillation
- Natural mode
- Child lock
- Sleep mode

### Cycling
- Speed level
Expand Down Expand Up @@ -51,6 +54,8 @@ platform: xiaomi_miio_airpurifier
| `type` | `custom:fan-xiaomi` | Mandatory: card type specification |
| `disable_animation` | `False` | Optional: Flag that defines whether to disable fan image |
| `disable_immediate_UI` | `False` | Optional: Flag that defines whether to disable immediate UI changes. In this case, UI is changed only when entity really changes the state. |
| `use_standard_speeds` | `False` | Optional: Use low/medium/high speeds instead of Level 1-3/4 for fans. This can be enabled, if fan is not yet properly supported by this card. |
| `force_sleep_mode_support` | `False` | Optional: Enables display of Sleep button in UI, which sets speed to 1% |
| `platform` | `xiaomi_miio_fan` | Optional: For [Xiaomi Mi Air Purifier & Xiaomi Mi Air Humidifier Integration](https://github.com/syssi/xiaomi_airpurifier) you must specify `xiaomi_miio_airpurifier`, if [Xiaomi Mi Smart Pedestal Fan Integration](https://github.com/syssi/xiaomi_fan) is used then can specify `xiaomi_miio_fan` or can ommit it. |

## Preview
Expand Down
164 changes: 134 additions & 30 deletions fan-xiaomi.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const LitElement = Object.getPrototypeOf(
customElements.get("ha-panel-lovelace")
customElements.get("ha-panel-lovelace")
);
const html = LitElement.prototype.html;
const includeDomains = ["fan"];
Expand All @@ -11,11 +11,13 @@ class FanXiaomi extends HTMLElement {
}

static getStubConfig() {
return { entity: "fan.fan", name: "Xiaomi Fan", platform: "xiaomi_miio_airpurifier", disable_animation: false, disable_immediate_UI: true }
return { entity: "fan.fan", name: "Xiaomi Fan", platform: "xiaomi_miio_airpurifier", disable_animation: false,
disable_immediate_UI: true }
}

supportedAttributes = {
angle: true, childLock: true, timer: true, rotationAngle: true, speedLevels: 4, natural_speed: true, natural_speed_reporting: true, supported_angles: [30, 60, 90, 120]
angle: true, childLock: true, timer: true, rotationAngle: true, speedLevels: 4, natural_speed: true,
natural_speed_reporting: true, supported_angles: [30, 60, 90, 120], sleep_mode: false
}

set hass(hass) {
Expand All @@ -26,7 +28,10 @@ class FanXiaomi extends HTMLElement {
const ui = this.getUI();
const platform = this.config.platform || 'xiaomi_miio_fan';
const disable_immediate_UI = this.config.disable_immediate_UI;
const use_standard_speeds = this.config.use_standard_speeds || false;
const force_sleep_mode_support = this.config.force_sleep_mode_support || false;


if (!this.card){
const card = document.createElement('ha-card');
card.className = 'fan-xiaomi'
Expand Down Expand Up @@ -67,11 +72,21 @@ class FanXiaomi extends HTMLElement {
this.supportedAttributes.supported_angles = [30, 60, 90, 120, 150];
}
if (['leshow.fan.ss4'].includes(attrs['model'])){
this.supportedAttributes.natural_speed = false;
this.supportedAttributes.natural_speed_reporting = false;
this.supportedAttributes.rotationAngle = false;
this.supportedAttributes.childLock = false;
this.supportedAttributes.sleep_mode = true;
}


//trick to support of 'any' fan
if (use_standard_speeds) {
this.supportedAttributes.speedList = ['low', 'medium', 'high']
}
if (force_sleep_mode_support) {
this.supportedAttributes.sleep_mode = true;
}

if (!this.card) {
const card = document.createElement('ha-card');
card.className = 'fan-xiaomi'
Expand Down Expand Up @@ -128,24 +143,25 @@ class FanXiaomi extends HTMLElement {
let iconSpan = u.querySelector('.icon-waper')
let icon = u.querySelector('.icon-waper > ha-icon').getAttribute('icon')
let newSpeedLevel
if (icon === "mdi:numeric-1-box-outline") {
newSpeedLevel = 2
} else if (icon === "mdi:numeric-2-box-outline") {
newSpeedLevel = 3
} else if (icon === "mdi:numeric-3-box-outline") {
newSpeedLevel = this.supportedAttributes.speedLevels == 3 ? 1 : 4
} else if (icon === "mdi:numeric-4-box-outline") {
newSpeedLevel = 1
let newSpeed

let maskSpeedLevel = /mdi:numeric-(\d)-box-outline/g
let speedLevelMatch = maskSpeedLevel.exec(icon)
let speedLevel = parseInt(speedLevelMatch ? speedLevelMatch[1] : 1)
if (use_standard_speeds) {
newSpeedLevel = this.supportedAttributes.speedList[(speedLevel <
this.supportedAttributes.speedList.length ? speedLevel: 0)]
newSpeed = newSpeedLevel
} else {
this.error(`Error setting fan speed. icon = ${icon}`)
newSpeedLevel = 1
this.error(`Defaulting to: ${newSpeedLevel}`)
newSpeedLevel = (speedLevel < this.supportedAttributes.speedLevels ? speedLevel+1: 1)
newSpeed = `Level ${newSpeedLevel}`
}


if (!disable_immediate_UI) {
iconSpan.innerHTML = `<ha-icon icon="mdi:numeric-${newSpeedLevel}-box-outline"></ha-icon>`
blades.className = `blades level${newSpeedLevel}`
}
let newSpeed = `Level ${newSpeedLevel}`
this.log(`Set speed to: ${newSpeed}`)
hass.callService('fan', 'set_speed', {
entity_id: entityId,
Expand Down Expand Up @@ -309,6 +325,33 @@ class FanXiaomi extends HTMLElement {
}
}

// Sleep mode event bindings
ui.querySelector('.var-sleep').onclick = () => {
this.log('Sleep')
if (ui.querySelector('.fanbox').classList.contains('active')) {
let u = ui.querySelector('.var-sleep')
if (u.classList.contains('active') === false) {
this.log(`Set sleep mode to: On`)
if (!disable_immediate_UI) {
u.classList.add('active')
}
hass.callService('fan', 'set_percentage', {
entity_id: entityId,
percentage: 1
});
} else {
this.log(`Set sleep mode to: Off`)
if (!disable_immediate_UI) {
u.classList.remove('active')
}
hass.callService('fan', 'set_speed', {
entity_id: entityId,
speed: 'low'
});
}
}
}

// Oscillation toggle event bindings
ui.querySelector('.var-oscillating').onclick = () => {
this.log('Oscillate')
Expand Down Expand Up @@ -348,6 +391,7 @@ class FanXiaomi extends HTMLElement {
title: myname || attrs['friendly_name'],
natural_speed: attrs['natural_speed'],
direct_speed: attrs['direct_speed'],
raw_speed: attrs['raw_speed'],
state: state.state,
child_lock: attrs['child_lock'],
oscillating: attrs['oscillating'],
Expand Down Expand Up @@ -520,14 +564,22 @@ Oscillate
Natural
</button>
</div>
<div class="op var-sleep">
<button>
<span class="icon-waper">
<ha-icon icon="mdi:power-sleep"></ha-icon>
</span>
Sleep
</button>
</div>
</div>
`
return fanbox
}

// Define UI Parameters

setUI(fanboxa, {title, natural_speed, direct_speed, state,
setUI(fanboxa, {title, natural_speed, direct_speed, raw_speed, state,
child_lock, oscillating, led_brightness, delay_off_countdown, angle,
speed, mode, model
}) {
Expand Down Expand Up @@ -558,7 +610,8 @@ Natural
if(delay_off_countdown) {
let total_mins = delay_off_countdown

if (['dmaker.fan.p15', 'dmaker.fan.p11', 'dmaker.fan.p10', 'dmaker.fan.p9', 'dmaker.fan.p5'].indexOf(model) === -1) {
if (['dmaker.fan.p15', 'dmaker.fan.p11', 'dmaker.fan.p10', 'dmaker.fan.p9', 'dmaker.fan.p5']
.indexOf(model) === -1) {
total_mins = total_mins / 60
}

Expand Down Expand Up @@ -607,23 +660,30 @@ Natural
} else {
activeElement.classList.remove('active')
}
// let direct_speed_int = Number(direct_speed)
let speedRegexp = /Level (\d)/g
let speedRegexpMatch = speedRegexp.exec(speed)
//let raw_speed_int = Number(raw_speed)
let speedRegexpMatch
let speedLevel
if (speedRegexpMatch && speedRegexpMatch.length > 0) {
speedLevel = speedRegexpMatch[1]
}
if (speedLevel === undefined) {
speedLevel = 1
let raw_speed_int = Number(raw_speed)
if (!this.config.use_standard_speeds) {
let speedRegexp = /Level (\d)/g
speedRegexpMatch = speedRegexp.exec(speed)
if (speedRegexpMatch && speedRegexpMatch.length > 0) {
speedLevel = speedRegexpMatch[1]
}
if (speedLevel === undefined) {
speedLevel = 1
}
} else {
let speedCount = this.supportedAttributes.speedList.length
speedLevel = Math.round(raw_speed_int/100*speedCount)
}
iconSpan.innerHTML = `<ha-icon icon="mdi:numeric-${speedLevel}-box-outline"></ha-icon>`
activeElement = fanboxa.querySelector('.fanbox .blades')
activeElement.className = `blades level${speedLevel}`

// Natural mode
activeElement = fanboxa.querySelector('.var-natural')

//p* fans do not report direct_speed and natural_speed
if (!this.supportedAttributes.natural_speed_reporting && this.supportedAttributes.natural_speed) {
if (mode === 'nature') {
Expand All @@ -644,7 +704,23 @@ Natural
} else {
activeElement.classList.remove('active')
}
} else {
} else
{
activeElement.style.display='none'
}

// Sleep mode
activeElement = fanboxa.querySelector('.var-sleep')
if (this.supportedAttributes.sleep_mode) {
if (raw_speed_int == 1) {
if (activeElement.classList.contains('active') === false) {
activeElement.classList.add('active')
}
} else {
activeElement.classList.remove('active')
}
} else
{
activeElement.style.display='none'
}

Expand Down Expand Up @@ -680,7 +756,7 @@ Natural

// Add to logs
log() {
// console.log(...arguments)
//console.log(...arguments)
}
warn() {
// console.log(...arguments)
Expand Down Expand Up @@ -713,26 +789,51 @@ class ContentCardEditor extends LitElement {
var fanRE = new RegExp("fan\.")
return html`
<div class="card-config">
<paper-input
<div class="row">
<paper-input
label="${this.hass.localize("ui.panel.lovelace.editor.card.generic.title")} (${this.hass.localize("ui.panel.lovelace.editor.card.config.optional")})"
.value="${this.config.name}"
.configValue="${"name"}"
@value-changed="${this._valueChanged}"
></paper-input>
</div>
<div class="row">
<ha-formfield label="Disable animation">
<ha-switch
.checked=${this.config.disable_animation}
.configValue="${'disable_animation'}"
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
</div>
<div class="row">
<ha-formfield label="Disable immediate UI">
<ha-switch
.checked=${this.config.disable_immediate_UI}
.configValue="${'disable_immediate_UI'}"
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
</div>
<div class="row">
<ha-formfield label="Use HA standard speeds (low/medium/high)">
<ha-switch
.checked=${this.config.use_standard_speeds}
.configValue="${'use_standard_speeds'}"
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
</div>
<div class="row">
<ha-formfield label="Show sleep mode button">
<ha-switch
.checked=${this.config.force_sleep_mode_support}
.configValue="${'force_sleep_mode_support'}"
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
</div>
<div class="row">
<paper-dropdown-menu
label="Platform"
.configValue=${'platform'}
Expand All @@ -746,6 +847,8 @@ class ContentCardEditor extends LitElement {
${(Object.values(OptionsPlatform)).map(item => html` <paper-item>${item}</paper-item> `)}
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="row">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
Expand All @@ -759,6 +862,7 @@ class ContentCardEditor extends LitElement {
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
</div>
</div>
`
}
Expand Down

0 comments on commit 97a4022

Please sign in to comment.