Closed jonathan-gatard closed 10 months ago
Hi,
I tried to write a quirk: But when i pair my switch, it doesn't use my quirk, dou you know why ?
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Checking quirks for LIVOLO TI0001 (00:12:4b:00:21:74:d7:08)
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'bellows.zigbee.application.EZSPCoordinator'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.gledopto.soposhgu10.SoposhGU10'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {11, 13} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.lutron.lzl4bwhl01remote.LutronLZL4BWHL01Remote2'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.netvox.z308e3ed.Z308E3ED'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.osram.a19twhite.A19TunableWhite'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {3} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.philips.rom001.PhilipsROM001'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.philips.rwl020.PhilipsRWL020'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1, 2} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.philips.rwl021.PhilipsRWL021'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1, 2} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.smartthings.multi.SmartthingsMultiPurposeSensor'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.smartthings.tag_v4.SmartThingsTagV4'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.xbee.xbee3_io.XBee3Sensor'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {232, 230} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.xbee.xbee_io.XBeeSensor'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {232, 230} {6}
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.xiaomi.mija.smoke.MijiaHoneywellSmokeDetectorSensor'>
2020-11-28 01:11:02 DEBUG (MainThread) [zigpy.quirks.registry] Fail because endpoint list mismatch: {1} {6}
I read during 2/3 hours the doc about zigbee cluster, and i find "DeviceType.DIMMER_SWITCH" and "LevelControl.cluster_id", I think i'm on the right path
channels: [26] extended_pan_id: "21:75:8D:19:00:4B:12:00"
livolo.py:
import copy
import time
import binascii
import asyncio
import zigpy.types as t
import zigpy.zdo.types as zdo_t
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Identify, OnOff, LevelControl
from zigpy.zcl import foundation
from typing import Optional, Union
from zhaquirks.const import (
CLUSTER_ID,
COMMAND,
COMMAND_OFF,
COMMAND_ON,
DEVICE_TYPE,
ENDPOINT_ID,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SHORT_PRESS,
TURN_OFF,
TURN_ON,
)
from . import LIVOLO
LIVOLO_CLUSTER_ID = 0x0001
APS_REPLY_TIMEOUT = 5
LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF = 0x0004
ON_OFF_COMMAND_TOGGLE = 0x0002
LEVELCONTROL_ATTRIBUTE_CURRENT_LEVEL = 0x0000
LEVELCONTROL_ATTRIBUTE_ON_OFF_TRANSITION_TIME = 0x0010
class LivoloSwitch (CustomDevice) :
"""TI0001 device."""
def __init__(self, application, ieee, nwk, replaces):
super().__init__(application, ieee, nwk, replaces)
self.debug("LivoloSwitch#init: ieee: %s, nwk: %s, replaces: %s, application: %s",
ieee, nwk, replaces, application)
def make_attribute(self, attrid, value):
attr = foundation.Attribute()
attr.attrid = attrid
attr.value = foundation.TypeValue()
attr.value.value = value
return attr
def to_status(self, val):
if val is None:
return "Unknown"
if val > 0:
return "On"
return "Off"
def handle_message(
self,
profile: int,
cluster: int,
src_ep: int,
dst_ep: int,
message: bytes,
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
):
self.debug("LivoloSwitch#handle_message profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, message: %s, dst_addressing: %s",
profile, cluster, src_ep, dst_ep, binascii.hexlify(message), dst_addressing)
if profile == 260 and src_ep == 6 and cluster == LIVOLO_CLUSTER_ID:
self.last_seen = time.time()
left_status = message[-1] & 1
right_status = message[-1] & 2
left_endpoint = self.endpoints[6]
right_endpoint = self.endpoints[7]
left_cluster = left_endpoint.in_clusters[OnOff.cluster_id]
right_cluster = right_endpoint.in_clusters[OnOff.cluster_id]
left_old_status = left_cluster.get("on_off")
right_old_status = right_cluster.get("on_off")
self.debug("from out: left: %s -> %s, right: %s -> %s",
self.to_status(left_old_status), self.to_status(left_status),
self.to_status(right_old_status), self.to_status(right_status))
if left_old_status != left_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = left_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.Command.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if left_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep6 -> %s, attrs = %s", self.to_status(left_status), [attrs])
left_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, [attrs], dst_addressing=dst_addressing
)
if right_old_status != right_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = right_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.Command.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if right_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep7 -> %s, attrs = %s", self.to_status(right_status), [attrs])
right_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, [attrs], dst_addressing=dst_addressing
)
return
elif cluster == zdo_t.ZDOCmd.Device_annce and dst_ep == 0:
self.debug("LivoloSwitch#handle_message poll nwk(OnOff#toggle): %s", self.nwk)
hdr = foundation.ZCLHeader.cluster(tsn=0, command_id=ON_OFF_COMMAND_TOGGLE)
# hdr.frame_control.disable_default_response = False
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[OnOff.cluster_id]
schema = cluster.server_commands[ON_OFF_COMMAND_TOGGLE][1]
data = hdr.serialize() + t.serialize([], schema)
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
tsk = loop.create_task(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
asyncio.run(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
return super().handle_message(profile, cluster, src_ep, dst_ep, message, dst_addressing=dst_addressing)
async def request(
self,
profile,
cluster,
src_ep,
dst_ep,
sequence,
data,
expect_reply=True,
timeout=APS_REPLY_TIMEOUT,
use_ieee=False,
):
self.debug("LivoloSwitch#request profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, sequence: %s, data %s, expect_reply: %s, timeout: %s, use_ieee: %s",
profile, cluster, src_ep, dst_ep, sequence, binascii.hexlify(data), expect_reply, timeout, use_ieee)
if profile == 260 and OnOff.cluster_id == cluster:
hdr, cdata = foundation.ZCLHeader.deserialize(data)
self.debug("LivoloSwitch#request hdr: command_id: %s, is_reply: %s, manufacturer: %s, tsn: %s",
hdr.command_id, hdr.is_reply, hdr.manufacturer, hdr.tsn)
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[LevelControl.cluster_id]
schema = cluster.server_commands[LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF][1]
channel = 1
channel_name = "left"
if src_ep == 7:
channel = 2
channel_name = "right"
level = 1
if data[-1] == 1:
level = 108
self.debug("send: livolo on: %s/%s, channel: %s/%s", data[-1] == 1, level, channel_name, channel)
hdr.command_id = LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF
new_data = hdr.serialize() + t.serialize([level, channel], schema)
return await super().request(profile, cluster.cluster_id, 6, 6, sequence, new_data,
expect_reply = expect_reply, timeout=timeout, use_ieee=use_ieee)
return await super().request(profile, cluster, src_ep, dst_ep, sequence, data,
expect_reply=expect_reply, timeout = timeout, use_ieee = use_ieee)
class LivoloOnOff (OnOff):
cluster_id = LIVOLO_CLUSTER_ID
signature = {
# <SimpleDescriptor endpoint=6 profile=260 device_type=0 device _version=1 input_clusters=[0, 6] output_clusters=[6]>
MODELS_INFO: [(LIVOLO, "TI0001 ")],
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id
],
OUTPUT_CLUSTERS: [
OnOff.cluster_id
],
},
},
}
replacement = {
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic,
Identify,
OnOff,
LivoloOnOff,
],
OUTPUT_CLUSTERS: [
LevelControl,
OnOff
],
},
7: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic,
Identify,
OnOff,
LivoloOnOff,
],
OUTPUT_CLUSTERS: [
LevelControl
],
}
},
}
There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.
Livolo devices not yet.
ZHA find the device but not get any entity
@nofateg How I can use you script? I use Home Assistant OS.
Any way to install "easily" and permanente between updates?
@nofateg How I can use you script? I use Home Assistant OS.
Any way to install "easily" and permanente between updates? 1: "easy" no permanent: bash script:
container_id=$(docker ps | grep raspberrypi4-homeassistant | awk '{print $1}') echo "container_id = ${container_id}" docker cp livolo ${container_id}:/usr/local/lib/python3.9/site-packages/zhaquirks
2. automation:
automation:
id: permit_join alias: permit_join
trigger: platform: time_pattern minutes: '/1' action:
livolo directory: init.py:
"""Module for LIVOLO devices."""
LIVOLO = "LIVOLO"
livolo.py:
Can some one that can coding explaining what this automation is making with the Zigbee network ??
automation:
automation:
- id: permit_join
alias: permit_join
trigger:
platform: time_pattern
minutes: '/1'
action:
- service: zha.permit
data:
duration: 60
And how long ??
Pleas @nofateg can you explaining for us "normal" user what your automation is doing with the Zigbee network or do you not knowing that ?
@MiguelAngelLV if you is configuring local quirks in ZHA https://github.com/zigpy/zha-device-handlers/discussions/693#discussioncomment-857274 and making the quirk file in the folder is being stored out of the HA container and is not deleted then you is updating HA .
I cant saying if the quirk is working OK or not but is looks like doing somthing with the data sent from the device and is harmless is not working so worse trying it.
Its sending permit joining every minute minute for 60 seconds = is holding your Zigbee network open 24/7 for joining so its very dangerous like leaving your home door key in the lock on the outside on the street and all can going in your home 24/7.
And then the person is not replaying i thinking that hi is very well knowing what is doing and dont like explaining it for some reason that i dont knowing !!
@Adminiuga can you taking one look on the automation (point 2) in https://github.com/zigpy/zha-device-handlers/issues/596#issuecomment-939064721 i think it shall being deleted !!!
Sorry for addressing you directly but i think its very bad if user is using the automation and not knowing what is doing with there system.
Thanks in advance.
The automation of @nofategm is because the Livolo Devices lose the sync some times.
I use livolo with z2m and I need open zigbee sync 30 each hour...
Still "open for sync" (= opening the network for joining devices) is still have your network open for all who like joining it and can getting all your network information inclusive you private network key.
If you have the device i recommending contacting the manufacture / seller and demanding that they is fixing the problem in the device firmware or returning them to the seller then its not behaving as Zigbee standard and cant staying in one "normal"zigbee network (with normal zigbee 3 security).
Its up to you but i wold putting them in the trash can or on the shelf until i have getting them working OK with new firmware.
When I toggle the livolo switch from the device, ZHA don't show the change. Only work from ZHA to Livolo, not vicebersa.
@nofategm does it work bidirectional for you?
HA configuration:
pi@raspberrypi:/usr/share/hassio/homeassistant/zha/livolo $ ls -la
total 28
drwxr-xr-x 3 pi pi 4096 Jan 21 14:08 .
drwxr-xr-x 3 root root 4096 Jan 21 15:38 ..
-rw-r--r-- 1 pi pi 53 Feb 10 2021 __init__.py
-rw-r--r-- 1 pi pi 9191 Feb 10 2021 livolo.py
drwxr-xr-x 2 root root 4096 Jan 21 14:08 __pycache__
It doesn't work for me. I have a rpi4 running hassio and ZHA with several sensors already configured and running (mostly AQARA) and a Zig-a-zig-ah! (ZZH) stick as coordinator ( CC2652R)
I have included this in my configuration.yaml:
zha:
custom_quirks_path: "/config/zha"
zigpy_config:
network:
channel: 26
channels: [26]
extended_pan_id: "21:75:8D:19:00:4B:12:00"
The I have created the zha/livolo path with the following files inside with your scripts:
livolo.py init.py
After restart I tried to find my 1-gang livolo switches but no luck.
Some tips from your side????
oops, init.py must be renamed to __init__.py
oops, init.py must be renamed to init.py
Renamed, but same result :-(
Hi again... now it works :-) but the trick was erase de NVRAM of my stick so it gets reseted at all. Then I plug it again in the raspberry and let it to form the network again from the scracth with the right configuration. I hope that the others sensors I have around make the channel migration by themselves.
Anyway, I have another problem now and I would like to check if your behaivour is the same. I can switch on and off the livolo from the HA dashboard, but if I switch on or off the livolo manually it doesn't send the new state to HA. So, it seems that the comunication is HA to livolo but not backwards. It happend the same in your system?
Thanks a lot in advance for your help ;-)
please add a full log
Anyway, I have another problem now and I would like to check if your behaivour is the same. I can switch on and off the livolo from the HA dashboard, but if I switch on or off the livolo manually it doesn't send the new state to HA. So, it seems that the comunication is HA to livolo but not backwards. It happend the same in your system?
Thanks a lot in advance for your help ;-)
Some here.
please add a full log
Sorry for my late answer... How can I get the full log you need?
@MiguelAngelLV Hola Miguel Ángel. ¿Has conseguido ver el estado de los interruptores en home assistant?. No sé cómo hacer para que al accionarlos manualmente envíen el nuevo estado. Un saludo
@quikote No :(
Solo me funciona de HA a Livolo, no de Livolo a HA.
He encargado un Sonoff USB Doggle a ver si con ese funciona, pero seguramente me tardará en llegar.
@quikote No :(
Solo me funciona de HA a Livolo, no de Livolo a HA.
He encargado un Sonoff USB Doggle a ver si con ese funciona, pero seguramente me tardará en llegar.
@MiguelAngelLV Pues no creo que te funcione. Yo también pillé uno y nada. Lo dejado por imposible, me he cambiado a zigbee2mqtt y mano de santo. Siguiendo las instrucciones todos los livolo pareados a la primera y funcionando perfectamente con un coordinador (sonoff usb dongle plus) y dos routers (un zzh y otro sonoff usb dongle). El resto de mis dispositivos zigbee que son aqara, hue y Ikea pareados sin cambiar el canal ni nada y funcionando. Me gustaba ZHA pero z2m le da mil vueltas y lo del mqtt si no lo usas para nada más no molesta.
I've been doing tests and got nothing.
It seems that there is some way to request the status of the device using the clusters, but I don't know very well how. In Z2M they have a pool every few seconds checking the status.
I have two issues:
This is the code of livolo socket in Z2M
key: ['state'],
convertSet: async (entity, key, value, meta) => {
if (typeof value !== 'string') {
return;
}
const state = value.toLowerCase();
let oldstate = 1;
if (state === 'on') {
oldstate = 108;
}
let channel = 1.0;
const postfix = meta.endpoint_name || 'left';
await entity.command('genOnOff', 'toggle', {}, {transactionSequenceNumber: 0});
const payloadOn = {0x0001: {value: Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]), type: 1}};
const payloadOff = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 1}};
const payloadOnRight = {0x0001: {value: Buffer.from([2, 0, 0, 0, 0, 0, 0, 0]), type: 2}};
const payloadOffRight = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 2}};
const payloadOnBottomLeft = {0x0001: {value: Buffer.from([4, 0, 0, 0, 0, 0, 0, 0]), type: 4}};
const payloadOffBottomLeft = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 4}};
const payloadOnBottomRight = {0x0001: {value: Buffer.from([8, 0, 0, 0, 0, 0, 0, 0]), type: 136}};
const payloadOffBottomRight = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 136}};
if (postfix === 'left') {
await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', {level: oldstate, transtime: channel});
await entity.write('genPowerCfg', (state === 'on') ? payloadOn : payloadOff,
{
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9,
});
return {state: {state_left: value.toUpperCase()}, readAfterWriteTime: 250};
} else if (postfix === 'right') {
channel = 2.0;
await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', {level: oldstate, transtime: channel});
await entity.write('genPowerCfg', (state === 'on') ? payloadOnRight : payloadOffRight,
{
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9,
});
return {state: {state_right: value.toUpperCase()}, readAfterWriteTime: 250};
} else if (postfix === 'bottom_right') {
await entity.write('genPowerCfg', (state === 'on') ? payloadOnBottomRight : payloadOffBottomRight,
{
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9,
});
return {state: {state_bottom_right: value.toUpperCase()}, readAfterWriteTime: 250};
} else if (postfix === 'bottom_left') {
await entity.write('genPowerCfg', (state === 'on') ? payloadOnBottomLeft : payloadOffBottomLeft,
{
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9,
});
return {state: {state_bottom_left: value.toUpperCase()}, readAfterWriteTime: 250};
}
return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250};
},
convertGet: async (entity, key, meta) => {
await entity.command('genOnOff', 'toggle', {}, {transactionSequenceNumber: 0});
},
And this for Livolo Switch
key: ['state'],
convertSet: async (entity, key, value, meta) => {
if (typeof value !== 'string') {
return;
}
const postfix = meta.endpoint_name || 'left';
let state = value.toLowerCase();
let channel = 1;
if (state === 'on') {
state = 108;
} else if (state === 'off') {
state = 1;
} else {
return;
}
if (postfix === 'left') {
channel = 1.0;
} else if (postfix === 'right') {
channel = 2.0;
} else {
return;
}
await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', {level: state, transtime: channel});
return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250};
},
convertGet: async (entity, key, meta) => {
await entity.command('genOnOff', 'toggle', {}, {transactionSequenceNumber: 0});
},
The command from HA to Livolo works perfect, better than Z2M, and, for now, I don't need keep paring mode on.
On the other hand, I have modified the script so that it detects the devices as lights instead of switches, in case someone needs it, since when I changed it from the HA configuration, when putting more than 3, ZHA stopped loading all the lights ( livolo or ikea bulbs)
import copy
import time
import binascii
import asyncio
import zigpy.types as t
import zigpy.zdo.types as zdo_t
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Identify, OnOff, Groups, Scenes, LevelControl
from zigpy.zcl.clusters.lightlink import LightLink
from zigpy.zcl import foundation
from typing import Optional, Union
from zhaquirks.const import (
CLUSTER_ID,
COMMAND,
COMMAND_OFF,
COMMAND_ON,
DEVICE_TYPE,
ENDPOINT_ID,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SHORT_PRESS,
TURN_OFF,
TURN_ON,
)
from . import LIVOLO
LIVOLO_CLUSTER_ID = 0x0001
APS_REPLY_TIMEOUT = 5
LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF = 0x0004
ON_OFF_COMMAND_TOGGLE = 0x0002
LEVELCONTROL_ATTRIBUTE_CURRENT_LEVEL = 0x0000
LEVELCONTROL_ATTRIBUTE_ON_OFF_TRANSITION_TIME = 0x0010
class LivoloSwitch (CustomDevice) :
"""TI0001 device."""
def __init__(self, application, ieee, nwk, replaces):
super().__init__(application, ieee, nwk, replaces)
self.debug("LivoloSwitch#init: ieee: %s, nwk: %s, replaces: %s, application: %s",
ieee, nwk, replaces, application)
def make_attribute(self, attrid, value):
attr = foundation.Attribute()
attr.attrid = attrid
attr.value = foundation.TypeValue()
attr.value.value = value
return attr
def to_status(self, val):
if val is None:
return "Unknown"
if val > 0:
return "On"
return "Off"
def handle_message(
self,
profile: int,
cluster: int,
src_ep: int,
dst_ep: int,
message: bytes,
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
):
self.debug("LivoloSwitch#handle_message profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, message: %s, dst_addressing: %s",
profile, cluster, src_ep, dst_ep, binascii.hexlify(message), dst_addressing)
if profile == 260 and src_ep == 6 and cluster == LIVOLO_CLUSTER_ID:
self.last_seen = time.time()
left_status = message[-1] & 1
right_status = message[-1] & 2
left_endpoint = self.endpoints[6]
right_endpoint = self.endpoints[7]
left_cluster = left_endpoint.in_clusters[OnOff.cluster_id]
right_cluster = right_endpoint.in_clusters[OnOff.cluster_id]
left_old_status = left_cluster.get("on_off")
right_old_status = right_cluster.get("on_off")
self.debug("from out: left: %s -> %s, right: %s -> %s",
self.to_status(left_old_status), self.to_status(left_status),
self.to_status(right_old_status), self.to_status(right_status))
if left_old_status != left_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = left_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.Command.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if left_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep6 -> %s, attrs = %s", self.to_status(left_status), [attrs])
left_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, [attrs], dst_addressing=dst_addressing
)
if right_old_status != right_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = right_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.Command.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if right_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep7 -> %s, attrs = %s", self.to_status(right_status), [attrs])
right_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, [attrs], dst_addressing=dst_addressing
)
return
elif cluster == zdo_t.ZDOCmd.Device_annce and dst_ep == 0:
self.debug("LivoloSwitch#handle_message poll nwk(OnOff#toggle): %s", self.nwk)
hdr = foundation.ZCLHeader.cluster(tsn=0, command_id=ON_OFF_COMMAND_TOGGLE)
# hdr.frame_control.disable_default_response = False
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[OnOff.cluster_id]
schema = cluster.server_commands[ON_OFF_COMMAND_TOGGLE][1]
data = hdr.serialize() + t.serialize([], schema)
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
tsk = loop.create_task(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
asyncio.run(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
return super().handle_message(profile, cluster, src_ep, dst_ep, message, dst_addressing=dst_addressing)
async def request(
self,
profile,
cluster,
src_ep,
dst_ep,
sequence,
data,
expect_reply=True,
timeout=APS_REPLY_TIMEOUT,
use_ieee=False,
):
self.debug("LivoloSwitch#request profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, sequence: %s, data %s, expect_reply: %s, timeout: %s, use_ieee: %s",
profile, cluster, src_ep, dst_ep, sequence, binascii.hexlify(data), expect_reply, timeout, use_ieee)
if profile == 260 and OnOff.cluster_id == cluster:
hdr, cdata = foundation.ZCLHeader.deserialize(data)
self.debug("LivoloSwitch#request hdr: command_id: %s, is_reply: %s, manufacturer: %s, tsn: %s",
hdr.command_id, hdr.is_reply, hdr.manufacturer, hdr.tsn)
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[LevelControl.cluster_id]
schema = cluster.server_commands[LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF][1]
channel = 1
channel_name = "left"
if src_ep == 7:
channel = 2
channel_name = "right"
level = 1
if data[-1] == 1:
level = 108
self.debug("send: livolo on: %s/%s, channel: %s/%s", data[-1] == 1, level, channel_name, channel)
hdr.command_id = LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF
new_data = hdr.serialize() + t.serialize([level, channel], schema)
return await super().request(profile, cluster.cluster_id, 6, 6, sequence, new_data,
expect_reply = expect_reply, timeout=timeout, use_ieee=use_ieee)
return await super().request(profile, cluster, src_ep, dst_ep, sequence, data,
expect_reply=expect_reply, timeout = timeout, use_ieee = use_ieee)
class LivoloOnOff (OnOff):
cluster_id = LIVOLO_CLUSTER_ID
signature = {
# <SimpleDescriptor endpoint=6 profile=260 device_type=0 device _version=1 input_clusters=[0, 6] output_clusters=[6]>
MODELS_INFO: [(LIVOLO, "TI0001 ")],
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id
],
OUTPUT_CLUSTERS: [
OnOff.cluster_id
],
},
},
}
replacement = {
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
OnOff.cluster_id,
LightLink.cluster_id,
],
OUTPUT_CLUSTERS: [
LightLink.cluster_id,
LevelControl.cluster_id
],
},
7: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
OnOff.cluster_id,
LightLink.cluster_id,
],
OUTPUT_CLUSTERS: [
LightLink.cluster_id,
LevelControl.cluster_id
],
}
},
}
@nofategm with the last HA update livolo not works anymore.
I try with you script and my custom script, nothing.
I get this error:
Can you see if there's anything in the (debug) logs when starting up Home Assistant or trying to control the device when using the custom quirk? It's possible that it needs to be updated due to ZCLv7/v8 changes in zigpy. Some logs could help with this.
Can you attach the diagnostics data from the ZHA tile on the devices and services screen in HA please?
Sorry, I need these devices so I change to Z2M again.
But, before change, I did some test and remember the problem is the variable schema, it's type is not iterable. The class name was ZCL or something.
The line that fail is:
data = hdr.serialize() + t.serialize([], schema)
When try serialize scheme.
Um... I have another Pi and CC2531. Tonight I will make another HA installation and get loggers.
import copy
import time
import binascii
import asyncio
import zigpy.types as t
import zigpy.zdo.types as zdo_t
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Identify, OnOff, LevelControl
from zigpy.zcl import foundation
from typing import Optional, Union
from zhaquirks.const import (
CLUSTER_ID,
COMMAND,
COMMAND_OFF,
COMMAND_ON,
DEVICE_TYPE,
ENDPOINT_ID,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SHORT_PRESS,
TURN_OFF,
TURN_ON,
)
from . import LIVOLO
LIVOLO_CLUSTER_ID = 0x0001
APS_REPLY_TIMEOUT = 5
LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF = 0x0004
ON_OFF_COMMAND_TOGGLE = 0x0002
LEVELCONTROL_ATTRIBUTE_CURRENT_LEVEL = 0x0000
LEVELCONTROL_ATTRIBUTE_ON_OFF_TRANSITION_TIME = 0x0010
class LivoloSwitch (CustomDevice) :
"""TI0001 device."""
def __init__(self, application, ieee, nwk, replaces):
super().__init__(application, ieee, nwk, replaces)
self.debug("LivoloSwitch#init: ieee: %s, nwk: %s, replaces: %s, application: %s",
ieee, nwk, replaces, application)
def make_attribute(self, attrid, value):
attr = foundation.Attribute()
attr.attrid = attrid
attr.value = foundation.TypeValue(t.uint8_t, value);
return attr
def to_status(self, val):
if val is None:
return "Unknown"
if val > 0:
return "On"
return "Off"
def handle_message(
self,
profile: int,
cluster: int,
src_ep: int,
dst_ep: int,
message: bytes,
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
):
self.debug("LivoloSwitch#handle_message profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, message: %s, dst_addressing: %s",
profile, cluster, src_ep, dst_ep, binascii.hexlify(message), dst_addressing)
if profile == 260 and src_ep == 6 and cluster == LIVOLO_CLUSTER_ID:
self.last_seen = time.time()
left_status = message[-1] & 1
right_status = message[-1] & 2
left_endpoint = self.endpoints[6]
right_endpoint = self.endpoints[7]
left_cluster = left_endpoint.in_clusters[OnOff.cluster_id]
right_cluster = right_endpoint.in_clusters[OnOff.cluster_id]
left_old_status = left_cluster.get("on_off")
right_old_status = right_cluster.get("on_off")
self.debug("from out: left: %s -> %s, right: %s -> %s",
self.to_status(left_old_status), self.to_status(left_status),
self.to_status(right_old_status), self.to_status(right_status))
if left_old_status != left_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = left_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.Command.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if left_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep6 -> %s, attrs = %s", self.to_status(left_status), [attrs])
cmd = foundation.GENERAL_COMMANDS[foundation.GeneralCommand.Report_Attributes].schema(attrs)
left_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, cmd, dst_addressing=dst_addressing
)
if right_old_status != right_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = right_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.Command.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if right_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep7 -> %s, attrs = %s", self.to_status(right_status), [attrs])
cmd = foundation.GENERAL_COMMANDS[foundation.GeneralCommand.Report_Attributes].schema(attrs)
right_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, cmd, dst_addressing=dst_addressing
)
return
elif cluster == zdo_t.ZDOCmd.Device_annce and dst_ep == 0:
self.debug("LivoloSwitch#handle_message poll nwk(OnOff#toggle): %s", self.nwk)
hdr = foundation.ZCLHeader.cluster(tsn=0, command_id=ON_OFF_COMMAND_TOGGLE)
# hdr.frame_control.disable_default_response = False
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[OnOff.cluster_id]
schema = cluster.server_commands[ON_OFF_COMMAND_TOGGLE][1]
data = hdr.serialize() + t.serialize([],())
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
tsk = loop.create_task(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
asyncio.run(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
return super().handle_message(profile, cluster, src_ep, dst_ep, message, dst_addressing=dst_addressing)
async def request(
self,
profile,
cluster,
src_ep,
dst_ep,
sequence,
data,
expect_reply=True,
timeout=APS_REPLY_TIMEOUT,
use_ieee=False,
):
self.debug("LivoloSwitch#request profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, sequence: %s, data %s, expect_reply: %s, timeout: %s, use_ieee: %s",
profile, cluster, src_ep, dst_ep, sequence, binascii.hexlify(data), expect_reply, timeout, use_ieee)
if profile == 260 and OnOff.cluster_id == cluster:
hdr, cdata = foundation.ZCLHeader.deserialize(data)
self.debug("LivoloSwitch#request hdr: command_id: %s, is_reply: %s, manufacturer: %s, tsn: %s",
hdr.command_id, hdr.is_reply, hdr.manufacturer, hdr.tsn)
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[LevelControl.cluster_id]
schema = cluster.server_commands[LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF][1]
channel = 1
channel_name = "left"
if src_ep == 7:
channel = 2
channel_name = "right"
level = 1
if data[-1] == 1:
level = 108
self.debug("send: livolo on %s/%s channel %s/%s schema %s", data[-1] == 1, level, channel_name, channel, schema)
hdr.command_id = LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF
new_data = hdr.serialize() + t.serialize([level, channel], [t.uint8_t, t.uint16_t])
return await super().request(profile, cluster.cluster_id, 6, 6, sequence, new_data,
expect_reply = expect_reply, timeout=timeout, use_ieee=use_ieee)
return await super().request(profile, cluster, src_ep, dst_ep, sequence, data,
expect_reply=expect_reply, timeout = timeout, use_ieee = use_ieee)
class LivoloOnOff (OnOff):
cluster_id = LIVOLO_CLUSTER_ID
signature = {
# <SimpleDescriptor endpoint=6 profile=260 device_type=0 device _version=1 input_clusters=[0, 6] output_clusters=[6]>
MODELS_INFO: [(LIVOLO, "TI0001 ")],
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id
],
OUTPUT_CLUSTERS: [
OnOff.cluster_id
],
},
},
}
replacement = {
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic,
Identify,
OnOff,
LivoloOnOff
],
OUTPUT_CLUSTERS: [
LevelControl,
OnOff
],
},
7: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic,
Identify,
OnOff,
LivoloOnOff,
],
OUTPUT_CLUSTERS: [
LevelControl
],
}
},
}
Still unresolved? Got a livolo switch 3 buttons and be damed if i'll buy their hub. (ZHA on raspberry pi with sonoff plus dongle)
Still not worked correctly. You can send data from HA to Livolo, but not read changes from device,
Any advance? It the only requirement for migrate from z2m to zha.
raspi 3 with zha and z2mqtt (2 sonoff dongle plus) . z2mqtt configured only for livolo(channel 26).works good, only problem is with power loss on raspi(need re-pair with controller every time but it keeps the config).
raspi 3 with zha and z2mqtt (2 sonoff dongle plus) . z2mqtt configured only for livolo(channel 26).works good, only problem is with power loss on raspi(need re-pair with controller every time but it keeps the config).
I have the same configuration and it works, but I want use ZHA.
For reparing I have a automation to enable pair mode after HA boot and it works most times.
Got a replacement for livolo 3 gang......now in testing mode . Moes 3 gang , on zha , it looks almost like livolo but dont have the red light in stand by(thats a bummer) and no sound click when turned on/off. Sent me the link for automation to test it pls.
Found a new one from aliexpress AVATTO switch. Tested one gang switch TS0011. Looks similar with moes but have click and blue light in standby. Adoption with zha works perfectly. It really looks like this is the one 🥇
I have a another very similar livolo switch in aliexpress: bseed
With the latest versions of home assistant all my livolos work without problems for 4 weeks!
import copy
import time
import binascii
import asyncio
import zigpy.types as t
import zigpy.zdo.types as zdo_t
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Identify, OnOff, LevelControl
from zigpy.zcl import foundation
from typing import Optional, Union
from zhaquirks.const import (
CLUSTER_ID,
COMMAND,
COMMAND_OFF,
COMMAND_ON,
DEVICE_TYPE,
ENDPOINT_ID,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SHORT_PRESS,
TURN_OFF,
TURN_ON,
)
from . import LIVOLO
LIVOLO_CLUSTER_ID = 0x0001
APS_REPLY_TIMEOUT = 5
LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF = 0x0004
ON_OFF_COMMAND_TOGGLE = 0x0002
LEVELCONTROL_ATTRIBUTE_CURRENT_LEVEL = 0x0000
LEVELCONTROL_ATTRIBUTE_ON_OFF_TRANSITION_TIME = 0x0010
class LivoloSwitch (CustomDevice) :
"""TI0001 device."""
def __init__(self, application, ieee, nwk, replaces):
super().__init__(application, ieee, nwk, replaces)
self.debug("LivoloSwitch#init: ieee: %s, nwk: %s, replaces: %s, application: %s",
ieee, nwk, replaces, application)
def make_attribute(self, attrid, value):
attr = foundation.Attribute()
attr.attrid = attrid
attr.value = foundation.TypeValue(t.uint8_t, value);
return attr
def to_status(self, val):
if val is None:
return "Unknown"
if val > 0:
return "On"
return "Off"
def handle_message(
self,
profile: int,
cluster: int,
src_ep: int,
dst_ep: int,
message: bytes,
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
):
self.debug("LivoloSwitch#handle_message profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, message: %s, dst_addressing: %s",
profile, cluster, src_ep, dst_ep, binascii.hexlify(message), dst_addressing)
if profile == 260 and src_ep == 6 and cluster == LIVOLO_CLUSTER_ID:
self.last_seen = time.time()
left_status = message[-1] & 1
right_status = message[-1] & 2
left_endpoint = self.endpoints[6]
right_endpoint = self.endpoints[7]
left_cluster = left_endpoint.in_clusters[OnOff.cluster_id]
right_cluster = right_endpoint.in_clusters[OnOff.cluster_id]
left_old_status = left_cluster.get("on_off")
right_old_status = right_cluster.get("on_off")
self.debug("from out: left: %s -> %s, right: %s -> %s",
self.to_status(left_old_status), self.to_status(left_status),
self.to_status(right_old_status), self.to_status(right_status))
if left_old_status != left_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = left_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.GeneralCommand.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if left_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep6 -> %s, attrs = %s", self.to_status(left_status), [attrs])
cmd = foundation.GENERAL_COMMANDS[foundation.GeneralCommand.Report_Attributes].schema(attrs)
left_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, cmd, dst_addressing=dst_addressing
)
if right_old_status != right_status:
frc = foundation.FrameControl(foundation.FrameType.GLOBAL_COMMAND)
tsn = right_endpoint.device.application.get_sequence()
hdr = foundation.ZCLHeader(frc, tsn=tsn, command_id=foundation.GeneralCommand.Report_Attributes)
hdr.frame_control.disable_default_response = True
attrs = []
if right_status > 0:
attrs = [self.make_attribute(1, 0), self.make_attribute(0, 1), self.make_attribute(2, 2)]
else:
attrs = [self.make_attribute(1, 1), self.make_attribute(0, 0), self.make_attribute(2, 2)]
self.debug("send to ep7 -> %s, attrs = %s", self.to_status(right_status), [attrs])
cmd = foundation.GENERAL_COMMANDS[foundation.GeneralCommand.Report_Attributes].schema(attrs)
right_endpoint.handle_message(
profile, OnOff.cluster_id, hdr, cmd, dst_addressing=dst_addressing
)
return
elif cluster == zdo_t.ZDOCmd.Device_annce and dst_ep == 0:
self.debug("LivoloSwitch#handle_message poll nwk(OnOff#toggle): %s", self.nwk)
hdr = foundation.ZCLHeader.cluster(tsn=0, command_id=ON_OFF_COMMAND_TOGGLE)
# hdr.frame_control.disable_default_response = False
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[OnOff.cluster_id]
schema = cluster.server_commands[ON_OFF_COMMAND_TOGGLE].name
data = hdr.serialize() + t.serialize([],())
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
tsk = loop.create_task(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
asyncio.run(super().request(zha.PROFILE_ID, OnOff.cluster_id, 6, 6, 0, data))
else:
return super().handle_message(profile, cluster, src_ep, dst_ep, message, dst_addressing=dst_addressing)
async def request(
self,
profile,
cluster,
src_ep,
dst_ep,
sequence,
data,
expect_reply=True,
timeout=APS_REPLY_TIMEOUT,
use_ieee=False,
):
self.debug("LivoloSwitch#request profile: %s, cluster: %s, src_ep: %s, dst_ep: %s, sequence: %s, data %s, expect_reply: %s, timeout: %s, use_ieee: %s",
profile, cluster, src_ep, dst_ep, sequence, binascii.hexlify(data), expect_reply, timeout, use_ieee)
if profile == 260 and OnOff.cluster_id == cluster:
hdr, cdata = foundation.ZCLHeader.deserialize(data)
self.debug("LivoloSwitch#request hdr: command_id: %s, manufacturer: %s, tsn: %s",
hdr.command_id, hdr.manufacturer, hdr.tsn)
endpoint = self.endpoints[6]
cluster = endpoint.out_clusters[LevelControl.cluster_id]
schema = cluster.server_commands[LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF].name
# schema = cluster.server_commands[LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF][1]
channel = 1
channel_name = "left"
if src_ep == 7:
channel = 2
channel_name = "right"
level = 1
if data[-1] == 1:
level = 108
self.debug("send: livolo on %s/%s channel %s/%s schema %s", data[-1] == 1, level, channel_name, channel, schema)
hdr.command_id = LEVELCONTROL_COMMAND_MOVE_TO_LEVEL_WITH_ON_OFF
new_data = hdr.serialize() + t.serialize([level, channel], [t.uint8_t, t.uint16_t])
return await super().request(profile, cluster.cluster_id, 6, 6, sequence, new_data,
expect_reply = expect_reply, timeout=timeout, use_ieee=use_ieee)
return await super().request(profile, cluster, src_ep, dst_ep, sequence, data,
expect_reply=expect_reply, timeout = timeout, use_ieee = use_ieee)
class LivoloOnOff (OnOff):
cluster_id = LIVOLO_CLUSTER_ID
signature = {
# <SimpleDescriptor endpoint=6 profile=260 device_type=0 device _version=1 input_clusters=[0, 6] output_clusters=[6]>
MODELS_INFO: [(LIVOLO, "TI0001 ")],
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id
],
OUTPUT_CLUSTERS: [
OnOff.cluster_id
],
},
},
}
replacement = {
ENDPOINTS: {
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
OnOff.cluster_id,
LivoloOnOff.cluster_id
],
OUTPUT_CLUSTERS: [
LevelControl.cluster_id,
OnOff.cluster_id
],
},
7: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
OnOff.cluster_id,
LivoloOnOff.cluster_id,
],
OUTPUT_CLUSTERS: [
LevelControl.cluster_id
],
}
},
}
configuration.xml:
zha:
zigpy_config:
network:
channels: [26]
extended_pan_id: "21:75:8D:19:00:4B:12:00"
automation:
automation:
- id: permit_join
alias: permit_join
trigger:
platform: time_pattern
seconds: '/30'
action:
- service: zha.permit
data:
duration: 30
@nofateg bidirectional? livolo -> zha, zha -> livolo?
yes, and nothing is lost
zigbee controller - sonoff zbbridge ( tasmota ) some livolo connect via tuya socket
There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.
https://github.com/Koenkk/zigbee2mqtt/issues/15299 this is a big problem with z2m
I purchased zb pro it works on a different protocol and the previous code does not start reading the status. Livolo uses the battery channel to detect the status of the switch, so there is a conflict with ha.
I tried several alternatives for livolo... and all bad.
I'm hoping to find a way to disable protocol checking.
Hello !
Describe the solution you'd like I see Livolo switch are not implemented, somebody could do it :) ?
Additional context I have a lot of informations about it !
This switch works only on channel 26, but i found a tips ! With a software (TestGUI_ZiGate_NXP-4 for me because i use ZiGate) i changed the channel to 26, then i paired the device on ZHA and finally i backed to channel 11 (but i don't have home assistant log because i pair the device on my Windows) I found this tips here: https://zigate.fr/documentation/changer-le-canal-zigbee-pour-les-appareils-livolo/
There is also informations for DomoticZ here: https://github.com/pipiche38/Domoticz-Zigate-Wiki/blob/master/en-eng/Livolo-corner.md
Device signature - this can be acquired by removing the device from ZHA and pairing it again from the add devices screen. Be sure to add the entire content of the log panel after pairing the device to a code block below this line.
Informations about switch: Zigbee info:
And this is the log when i pair the switch :
I think the problem is cluster 0x0006, because i see on the domoticz github that Livolo switch uses 0x0008 !
If you have question don't hesitate !
Thanks !