Skip to content

Instantly share code, notes, and snippets.

@gwww
Last active July 7, 2024 22:58
Show Gist options
  • Save gwww/1c959a00ec2bcf59f7041fe731b33a3c to your computer and use it in GitHub Desktop.
Save gwww/1c959a00ec2bcf59f7041fe731b33a3c to your computer and use it in GitHub Desktop.
Script and config to disable Home Assistant entities and to "template" configurate an MQTT device
#!python3
# Based on: https://community.home-assistant.io/t/api-for-changing-entities/282114/3
import asyncio
import json
import os
import re
import sys
import yaml # type: ignore
import websockets
async def send(ws, data):
"""Send on websocket."""
json_str = json.dumps(data)
# print(f">>>>>> {json_str}")
await ws.send(json_str)
async def recv(ws):
"""Return data received on websocket."""
str = await ws.recv()
# print(f"<<<<<< {str}")
data = json.loads(str)
return data
def error(str):
print(str)
exit(1)
def next_id():
"""Return a unique monotonically incrementing ID."""
try:
next_id.id += 1
except AttributeError:
next_id.id = 100
return next_id.id
def read_config():
with open("disable-entities.yaml", "r") as stream:
try:
config = yaml.safe_load(stream)
except yaml.YAMLError as exc:
print(exc)
exit(2)
if not config["token"]:
config["token"] = os.environ.get("HA_ACCESS_TOKEN")
return config
def success(result):
return f"{'Success' if result['success'] else 'Failed!!!'}"
async def get_entities(ws):
"""Returns a list of enabled HA entities."""
# await send(ws, {"id": next_id(), "type": "config/entity_registry/list"})
await send(ws, {"id": next_id(), "type": "get_states"})
result = await recv(ws)
states = [entity["entity_id"] for entity in result["result"]]
return states
async def auth(ws, config):
auth_required = await recv(ws)
if auth_required["type"] != "auth_required":
error(f"Protocol error, auth_required expected, got: {auth_required}")
await send(ws, {"type": "auth", "access_token": config["token"]})
auth_status = await recv(ws)
if auth_status["type"] != "auth_ok":
error(f"Auth failed. {auth_status}")
async def disable_ha_entities(ws, config):
"""Disable the entities specified in the configuration YAML."""
ha_existing_entities = await get_entities(ws)
entities_to_update = []
for entity in config.get("disable_entities", []):
entity = entity.replace("%", config["_device_name"]).lower()
regex = re.compile(entity)
entities_to_update += list(filter(regex.match, ha_existing_entities))
entities_to_update = list(set(entities_to_update)) # Filter duplicates
entities_to_update.sort()
if entities_to_update:
print("List of Home Assistant entities that will be disabled:")
for entity in entities_to_update:
print(f" {entity}")
print('To confirm disabling type "yes": ', end="")
if input() != "yes":
print("Cancelling")
exit(4)
else:
print("No Home Assistant entities found to disable.")
for entity in entities_to_update:
update_msg = {
"type": "config/entity_registry/update",
"id": next_id(),
"disabled_by": "user",
"entity_id": entity,
}
await send(ws, update_msg)
result = await recv(ws)
print(f"{success(result)} updating {entity}")
print()
async def ha_mqtt_service(ws, topic, payload):
msg = {
"type": "call_service",
"id": next_id(),
"domain": "mqtt",
"service": "publish",
"service_data": {
"topic": topic,
"payload": json.dumps(payload),
},
}
await send(ws, msg)
return await recv(ws)
async def set_attribute(ws, device, attr, value):
result = await ha_mqtt_service(ws, f"zigbee2mqtt/{device}/set", {attr: value})
print(f"{success(result)} setting attribute {attr}={value}")
async def set_reporting(ws, device, report):
report["id"] = f"{device}/{report['endpoint']}"
del report["endpoint"]
result = await ha_mqtt_service(
ws, "zigbee2mqtt/bridge/request/device/configure_reporting", report
)
print(f"{success(result)} setting report threshold {report['attribute']}")
async def configure_device(ws, config):
for attr, value in config.get("device_configuration", {}).items():
await set_attribute(ws, config["_device_name"], attr, value)
for report in config.get("reporting_configuration", []):
await set_reporting(ws, config["_device_name"], report)
async def main():
config = read_config()
config["_device_name"] = sys.argv[1]
async with websockets.connect(f"ws://{config['server']}/api/websocket") as ws: # type: ignore
await auth(ws, config)
await disable_ha_entities(ws, config)
await configure_device(ws, config)
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <name of device>")
exit(1)
try:
asyncio.run(main())
except KeyboardInterrupt:
exit(3)
# Server IP or name
server: localhost:8123
token: <token from Home Assistant here>
# Disable Home Assistant entities that match the regular expression.
# The device name is substituted in for the '%' character in the entity_name.
disable_entities:
- '(number|sensor).%_default.*'
- '(number|select).%'
- 'sensor.%_remoteprotection'
- 'sensor.%_powertype'
- 'binary_sensor.%_update_available'
# Configure MQTT device attributes.
# All values are integer; to find name to value mapping see here:
# https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/devices/inovelli.js
device_configuration:
outputMode: 0 # Dimmer (vs On/Off switch)
relayClick: 1 # Disable the relay click, oddly 1 is disable
stateAfterPowerRestored: 255 # Restore to previous level after power restore
defaultLevelLocal: 254 # Turn on full when paddle pressed
defaultLevelRemote: 254 # Turn on full when controlled over Zigbee
# Configure MQTT reporting entries.
reporting_configuration:
- "endpoint": 1
"cluster": "haElectricalMeasurement"
"attribute": "activePower"
"minimum_report_interval": 1
"maximum_report_interval": 3600
"reportable_change": 10
- "endpoint": 1
"cluster": "seMetering"
"attribute": "currentSummDelivered"
"minimum_report_interval": 1
"maximum_report_interval": 3600
"reportable_change": 10
@zanix
Copy link

zanix commented Feb 8, 2024

Yep!
You can run this from any computer that has python installed.
Install some pip packages first:

pip install pyyaml websockets

Then run the script with the name of the device

python disable-entities.py "<device name>"

The only part I don't recall is what the device name should be. It was either the entity_id or the friendly name.

@chansearrington
Copy link

@zanix thanks!

So I have to do this for every device individually?

I was hoping I could do it to them all at once. Right now, I've got over 100 Inovelli blue switches. Doing this 1x at a time is time consuming. But I guess not as time consuming as disabling all of the entities individually or in groups.

@rccoleman
Copy link

rccoleman commented Mar 4, 2024

This worked great! @chansearrington you can replace % in the patterns with * to have it match anything up to the suffix for each pattern. That will modify all the entities for all the devices at once. Take a look at the entities that it lists before saying "yes" to make sure that it looks right.

@zanix
Copy link

zanix commented May 15, 2024

More entities for firmware 2.18

- number.%_fanledleveltype
- number.%_*forfancontrolmode
- number.%_quickstart*

- select.%_fancontrolmode
- select.%_fantimermode
- select.%_singletapbehavior

- sensor.%_devicebindnumber
- sensor.%_internaltemperature
- sensor.%_overheat

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment