Skip to content

Commit

Permalink
MQTT Discovery and Validation JS
Browse files Browse the repository at this point in the history
  • Loading branch information
Sian-Lee-SA committed Dec 4, 2022
1 parent 167ac59 commit 2b7d72b
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 1,972 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ In the side panel you goto Switch Manager. Next click `Add Switch` and select th

Once you've selected the blueprint, you will be taken to the switch editor view. There will be an identifier or mqtt topic input box up in the top left with a placeholder asking for the value for that key within the event data or mqtt topic.

You can either enter the identifier manually or use the button on the right (events only as this does not support mqtt) then press a button on the switch to auto fill the value. There is a posibility that an identifier from some other device for the event to be discovered if that device sent an event before your button push. If this is the case and the button helper isn't getting the right identifier then follow the next step to discover it manually.
You can either enter the identifier manually or use the button on the right then press a button on the switch to auto fill the value. There is a posibility that an identifier from some other device for the event to be discovered if that device sent an event before your button push. If this is the case and the button helper isn't getting the right identifier then follow the next step to discover it manually.

If you do not know the event value then goto Developer Tools -> Events and start listening for events (use * if you're unsure of the event type for your switch). Once you've started listening for events, push a button on your switch then stop the listener. View the data and you will find the event related to your switch. Inside that data you will find the identifier's value. Copy this value to the identifier's textbox on the switch editor page to bind.
* If you do not know the event value then goto Developer Tools -> Events and start listening for events (use * if you're unsure of the event type for your switch). Once you've started listening for events, push a button on your switch then stop the listener. View the data and you will find the event related to your switch. Inside that data you will find the identifier's value. Copy this value to the identifier's textbox on the switch editor page to bind.

Depending on the blueprint and the actions that your switch supports, you can select buttons by clicking on them from the image displayed and each button can have multiple actions eg tap, double tap and hold etc.

Expand Down Expand Up @@ -71,7 +71,7 @@ name | `string` | * | A friendly name for the switch
service | `string` | * | The service or integration that this switch relates to (matching services will be grouped when selecting a blueprint from gui)
event_type | `string` | * | Must match the event type through the event bus triggered by the switch (Monitor `*` events in developer tools if unsure of its value). Set this to mqtt if handling a mqtt message instead of an event (see [mqtt](#mqtt))
identifier_key | `string` | * | The key in the event data that will uniquely identify a switch (user input from the switch editor will allow entering it's value). **This property is ignore if using mqtt but is still currently required
mqtt_topic_format| `string` | - | If event_type is mqtt, then this will give the user an understanding of what they should set their topic as. example: zigbee2mqtt/{device}/action
mqtt_topic_format| `string` | - | If event_type is mqtt, then this will give the user an understanding of what they should set their topic as. example: zigbee2mqtt/+/action. The MQTT topic here will also help with discovery (remember to use wilcard + where needed)
buttons | `list` [Button](#button) | * | You will need to define a list of buttons even if the switch has only one or multiple. See [Button](#button) for details on defining a button
conditions | `list` [Condition](#condition) | - | This optional list allows the switch to only accept these conditions within the event data or mqtt payload. All conditions must evaluate to true to be valid. See [Condition](#condition) for details on defining a condition

Expand Down Expand Up @@ -118,6 +118,8 @@ value | `string` | * | The value to match for the key

MQTT is handled differently to events and the incoming data is that of a payload... If a payload is not json formatted then it will be passed in as the key `payload` containing the string. The payload itself is what the conditions will check against.

To help discover a switch when trying to discover from GUI then use a format for the topic in `mqtt_topic_format` that will scope down to the best possibility. For zigbee2mqtt this is generally `zigbee2mqtt/+/action`. The `+` is a wild card saying to match a single level (so anything between the backslash). For more information visit [here](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/)

If you want a condition on a payload that isn't json formatted then you would do as follows:
```yaml
- key: payload
Expand Down
23 changes: 12 additions & 11 deletions custom_components/switch_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,12 @@ async def async_setup( hass: HomeAssistant, config: Config ):
# Init hass storage
await hass.data[DOMAIN][CONF_STORE].load()

await async_migrate(hass)
await deploy_blueprints( hass )
is_dev = config.get(DOMAIN, {}).get('dev', False)
if is_dev:
LOGGER.warning('In Developer Mode')

await async_migrate( hass, is_dev )

_init_blueprints(hass)
await _init_switch_configs(hass)

Expand All @@ -102,19 +106,16 @@ async def async_setup_entry( hass, config_entry ):

return True

async def async_migrate( hass ):
# Opening JSON file
async def async_migrate( hass, in_dev ):
manifest = await load_manifest()
store = hass.data[DOMAIN][CONF_STORE]
if not store.compare_version( manifest['version'] ):

version_update = not store.compare_version( manifest['version'] )
if in_dev or version_update or not await check_blueprints_folder_exists( hass ):
LOGGER.debug('Migrating blueprints')
await deploy_blueprints( hass )
await store.update_version( manifest['version'] )
return True
elif not await check_blueprints_folder_exists( hass ):
await deploy_blueprints( hass )

return False
if version_update:
await store.update_version( manifest['version'] )

def _init_blueprints( hass: HomeAssistant ):
# Ensure blueprints empty for clean state
Expand Down
2,033 changes: 107 additions & 1,926 deletions custom_components/switch_manager/assets/switch_manager_panel.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Sonoff SNZB 01
service: Zigbee2MQTT
event_type: mqtt
identifier_key: topic
mqtt_topic_format: zigbee2mqtt/{device}/action
mqtt_topic_format: zigbee2mqtt/+/action
buttons:
- actions:
- title: tap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Tuya 4 Button Scene
service: Zigbee2MQTT
event_type: mqtt
identifier_key: topic
mqtt_topic_format: zigbee2mqtt/{device}/action
mqtt_topic_format: zigbee2mqtt/+/action
buttons:
## Not sure why the buttons are back to front and illogical by their numbers??
- x: 23
Expand Down
2 changes: 1 addition & 1 deletion custom_components/switch_manager/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"domain": "switch_manager",
"name": "Switch Manager",
"version": "0.10.1",
"version": "0.1.2",
"documentation": "https://github.com/Sian-Lee-SA/switch_manager",
"dependencies": ["panel_custom", "websocket_api", "http", "frontend", "script", "mqtt"],
"requirements": [],
Expand Down
3 changes: 0 additions & 3 deletions custom_components/switch_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,6 @@ def stop(self):

def setEnabled( self, value: bool ):
self.enabled = value




def _check_conditons( self, data ) -> bool:
if self.blueprint.event_type != 'mqtt':
Expand Down
8 changes: 4 additions & 4 deletions js/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ export function navigate(ev)
}

export const loadComponents = async() => {
if(customElements.get('ha-automation-action') && customElements.get('ha-data-table'))
/*if(customElements.get('ha-automation-action') && customElements.get('ha-data-table'))
{
return;
}
}*/

await customElements.whenDefined("partial-panel-resolver");
const ppResolver = document.createElement("partial-panel-resolver");
Expand All @@ -111,8 +111,8 @@ export const loadComponents = async() => {
await routes?.routes?.a?.load?.();
await customElements.whenDefined("ha-panel-config");
const configRouter: any = document.createElement("ha-panel-config");
// await configRouter?.routerOptions?.routes?.dashboard?.load?.(); // Load ha-config-dashboard
await configRouter?.routerOptions?.routes?.script?.load?.(); // Load ha-data-table
await configRouter?.routerOptions?.routes?.dashboard?.load?.(); // Load ha-config-dashboard
await configRouter?.routerOptions?.routes?.script?.load?.(); // Load ha-data-table, editor
await customElements.whenDefined("ha-config-dashboard");
}

Expand Down
70 changes: 53 additions & 17 deletions js/switch-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
mdiGestureTapButton,
mdiEarHearing
} from "@mdi/js";
import { MODES, SwitchManagerBlueprint, SwitchManagerConfig, SwitchManagerConfigButton } from "./types"
import { haStyle } from "@hass/resources/styles"
import { MODES, SwitchManagerBlueprint, SwitchManagerBlueprintCondition, SwitchManagerConfig, SwitchManagerConfigButton } from "./types";
import { haStyle } from "@hass/resources/styles";
import { subscribeMQTTTopic } from "@hass/data/mqtt";
import {
buildAssetUrl,
buildUrl,
Expand Down Expand Up @@ -134,7 +135,7 @@ class SwitchManagerSwitchEditor extends LitElement
required="true"
.label=${this.blueprint.event_type == 'mqtt'? 'mqtt topic' : this.blueprint?.identifier_key}
@input="${this._identifierChanged}"></ha-textfield>
${this.blueprint.event_type != 'mqtt' ? html`
${this.blueprint.event_type != 'mqtt' || this.blueprint.mqtt_topic_format ? html`
<ha-icon-button
.path=${mdiEarHearing}
?listening=${(this._subscribed)}
Expand Down Expand Up @@ -423,9 +424,9 @@ class SwitchManagerSwitchEditor extends LitElement
private async _drawSVG()
{
if( !this.blueprint.has_image )
{
return;
}

// Ensure SVG is in DOM
await this.updateComplete;
var img = new Image;
img.src = buildAssetUrl(`${this.blueprint.id}.png`);
Expand Down Expand Up @@ -539,12 +540,11 @@ class SwitchManagerSwitchEditor extends LitElement
{
if( index == this.button_index )
return;

this.button_index = index;

this.svg.querySelector('[selected]').removeAttribute('selected');
this.svg.querySelector(`[index="${index}"]`).setAttribute('selected', '');

this.button_index = index;

this._setActionIndex(0);
}

Expand Down Expand Up @@ -572,15 +572,51 @@ class SwitchManagerSwitchEditor extends LitElement
return;
}

this._subscribed = await this.hass!.connection.subscribeEvents( (event) => {
if( this.blueprint.identifier_key in event.data )
{
this.identifier_input.value = event.data[this.blueprint.identifier_key];
this._identifierChanged();
this._subscribed();
this._subscribed = undefined;
}
}, this.blueprint.event_type )
const __process_conditions = ( conditions: SwitchManagerBlueprintCondition[], data: any): boolean =>
{
if( ! conditions )
return true;

for( const condition of conditions )
if( !(condition.key in data) || String(condition.value) != String(data[condition.key]) )
return false;

return true;
}

const __validate_data = ( data: any ): boolean => {
if( ! __process_conditions( this.blueprint.conditions, data ) )
return false;

for( const button of this.blueprint.buttons )
if( __process_conditions(button.conditions, data) )
for( const action of button.actions )
if( __process_conditions(action.conditions, data) )
return true;

return false;
}

const __handle_response = ( id: string, data: any ) => {
if( !__validate_data(data) )
return;

this.identifier_input.value = id;
this._identifierChanged();
this._subscribed();
this._subscribed = undefined;
}

if( this.blueprint.event_type == 'mqtt' && this.blueprint.mqtt_topic_format )
this._subscribed = await subscribeMQTTTopic( this.hass, this.blueprint.mqtt_topic_format, (message) => {
const data = typeof(message.payload) == 'string' ? { payload: message.payload } : message.payload;
__handle_response( message.topic, data );
});
else
this._subscribed = await this.hass!.connection.subscribeEvents( (event) => {
if( this.blueprint.identifier_key in event.data )
__handle_response( event.data[this.blueprint.identifier_key], event.data );
}, this.blueprint.event_type );
}

private _identifierChanged(ev?: CustomEvent)
Expand Down
15 changes: 12 additions & 3 deletions js/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ export const isMaxMode = (
): mode is typeof MODES_MAX[number] =>
MODES_MAX.includes(mode as typeof MODES_MAX[number]);

export interface SwitchManagerBlueprintCondition
{
key: string;
value: string;
}

export interface SwitchManagerBlueprint
{
id: string;
name: string;
service: string;
has_image: boolean;
identifier_key: string;
mqtt_topic_format: string;
event_type: string;
identifier_key?: string;
conditions?: SwitchManagerBlueprintCondition[],
has_image: boolean;
mqtt_topic_format?: string;
buttons: SwitchManagerBlueprintButton[];
}

Expand All @@ -25,12 +32,14 @@ export interface SwitchManagerBlueprintButton
width: number;
height: number;
shape: string;
conditions?: SwitchManagerBlueprintCondition[],
actions: SwitchManagerBlueprintButtonAction[];
}

export interface SwitchManagerBlueprintButtonAction
{
title: string;
conditions?: SwitchManagerBlueprintCondition[],
}

export interface SwitchManagerConfig
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "switch_manager",
"private": true,
"version": "0.10.1",
"version": "0.1.2",
"description": "Home Assistant Frontend for Switch Manager",
"scripts": {
"build": "rollup -c",
"watch": "rollup -c --watch",
"update_live": "cp -r ./custom_components/switch_manager/* ../../home-assistant/config/custom_components/switch_manager/"
"update_live": "cp -r ./custom_components/switch_manager/* ../../home-assistant/config/custom_components/switch_manager/; sudo docker restart Home-Assistant;"
},
"keywords": [],
"author": "Sian",
Expand Down

0 comments on commit 2b7d72b

Please sign in to comment.