Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve HID driver support for hasseb #146

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
/debian/files
/debian/python3-dali*
/debian/.debhelper
/.venv/
100 changes: 75 additions & 25 deletions dali/driver/hid.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ async def _bus_watch(self):
# Fall through to continue processing frame
elif isinstance(frame, dali.frame.BackwardFrame):
# Error: config commands don't get backward frames.
self._log.error("Failed config command %s with backward frame",
self._log.debug("Failed config command %s with backward frame",
current_command)
self.bus_traffic._invoke(current_command, None, True)
current_command = None
Expand Down Expand Up @@ -702,10 +702,19 @@ class hasseb(hid):
will deadlock.
"""
_cmdtmpl = struct.Struct("BB")
_NO_DATA_AVAILABLE = 0
_NO_ANSWER = 1
_OK = 2
_INVALID_ANSWER = 3

_HASSEB_READ_FIRMWARE_VERSION = 0x02
_HASSEB_DALI_FRAME = 0X07

_sn = 0

_HASSEB_DRIVER_NO_DATA_AVAILABLE = 0
_HASSEB_DRIVER_NO_ANSWER = 1
_HASSEB_DRIVER_OK = 2
_HASSEB_DRIVER_INVALID_ANSWER = 3
_HASSEB_DRIVER_TOO_EARLY = 4
_HASSEB_DRIVER_SNIFFER_BYTE = 5
_HASSEB_DRIVER_SNIFFER_BYTE_ERROR = 6

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -714,40 +723,72 @@ def __init__(self, *args, **kwargs):
self._response_available = asyncio.Event()
self._response = None

def _initialise_device(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when is this method called ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 143 self._initialise_device(), it overrides hid class method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make a call to super()._initialise_device() and don't forget to set connected

self._sn+=1
if self._sn > 255:
self._sn = 1
# Read firmware version; pick up the reply in _handle_read
data = struct.pack('BBBBBBBBBB', 0xAA, self._HASSEB_READ_FIRMWARE_VERSION,
self._sn, 0, 0, 0, 0, 0, 0, 0)
os.write(self._f, data)

def _construct(self, command):
# sequence number
self._sn+=1
if self._sn > 255:
self._sn = 1
frame_length = 16
if command.is_query:
expect_reply = 1
else:
expect_reply = 0
transmitter_settling_time = 0
if command.sendtwice:
send_twice = 10 # 10 ms delay between messages
else:
send_twice = 0
frame = command.frame.as_byte_sequence
byte_a, byte_b = frame
data = struct.pack('BBBBBBBBBB', 0xAA, self._HASSEB_DALI_FRAME, self._sn,
frame_length, expect_reply,
transmitter_settling_time, send_twice,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is send_twice actually sent twice !?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs further testing to confirm, however in my testing the device sends it when send_twice is set > 0. value of send twice is used as delay in ms between the two sends.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are creating a single packet, with a format which I haven't seen documented, can you link where you saw this struct ?

byte_a, byte_b,
0)
return data


async def _send_raw(self, command):
frame = command.frame
if len(frame) != 16:
raise UnsupportedFrameTypeError
await self.connected.wait()
async with self._command_lock:
times = 2 if command.sendtwice else 1
for rep in range(times):
os.write(self._f, frame.pack_len(2))
data = self._construct(command)
os.write(self._f, data)
# Earlier commands may have left a response available that
# we need to ignore. We're only interested in responses
# that become available in the future.
self._response_available.clear()
response = None
if command.response:
# The hasseb device appears to have an internal table
# of which commands require responses. If a command
# does not require a response, it won't reply to us at
# all. Only wait for a reply if one is needed.
#
# NB there is NO WAY that this can be reliable: it
# won't understand commands from IEC-62386 part 202,
# for example.
# # The hasseb device appears to have an internal table
# # of which commands require responses. If a command
# # does not require a response, it won't reply to us at
# # all. Only wait for a reply if one is needed.
# #
# # NB there is NO WAY that this can be reliable: it
# # won't understand commands from IEC-62386 part 202,
# # for example.
await self._response_available.wait()
self._response_available.clear()
if self._response == "fail":
raise CommunicationError
elif self._response[0] == self._NO_ANSWER:
elif self._response[3] == self._HASSEB_DRIVER_NO_ANSWER:
response = command.response(None)
elif self._response[0] == self._OK:
response = command.response(dali.frame.BackwardFrame(self._response[1]))
elif self._response[0] == self._INVALID_ANSWER:
response = command._response(dali.frame.BackwardFrameError(
self._response[1]))
elif self._response[3] == self._HASSEB_DRIVER_OK and data[4] == 1:
response = command.response(dali.frame.BackwardFrame(self._response[5]))
elif self._response[3] == self._HASSEB_DRIVER_INVALID_ANSWER:
response = command._response(dali.frame.BackwardFrameError(255))
else:
self._log.debug("Unknown response code %x", self._response[0])

Expand All @@ -758,9 +799,18 @@ def _handle_read(self, data):
# Response should be two bytes. First byte is status, second
# byte is optional response data. The hasseb appears to send
# "NO DATA AVAILABLE" reports continuously when it is idle.
if data[0] != self._NO_DATA_AVAILABLE:
self._response = data
self._response_available.set()

if len(data)==10:
if data[1] == self._HASSEB_DRIVER_NO_DATA_AVAILABLE:
return None
if data[1] == self._HASSEB_READ_FIRMWARE_VERSION:
# print(f"{data[3]}.{data[4]}")
self.firmware_version = f"{data[3]}.{data[4]}"
self.connected.set()
elif data[1] == self._HASSEB_DALI_FRAME:
self._response = data
self._response_available.set()


def _shutdown_device(self):
# If there's a command in progress, tell it it's failed
Expand Down
4 changes: 2 additions & 2 deletions examples/async-flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

async def main():
# Edit to pick a device type.
dev = tridonic("/dev/dali/daliusb-*", glob=True)
#dev = hasseb("/dev/dali/hasseb-*", glob=True)
# dev = tridonic("/dev/dali/daliusb-*", glob=True)
dev = hasseb("/dev/dali/hasseb-*", glob=True)
dev.connect()
print("Waiting to be connected...")
await dev.connected.wait()
Expand Down
5 changes: 3 additions & 2 deletions examples/async-tc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from dali.gear.general import QueryControlGearPresent, QueryActualLevel
from dali.gear.led import QueryDimmingCurve
from dali.gear.sequences import SetDT8ColourValueTc, SetDT8TcLimit, QueryDT8ColourValue
from dali.driver.hid import tridonic
from dali.driver.hid import tridonic, hasseb
from dali.sequences import QueryDeviceTypes, DALISequenceError

def print_command_and_response(dev, command, response, config_command_error):
Expand Down Expand Up @@ -62,7 +62,8 @@ async def scan_control_gear(d, detailed):
print(f"{addr}: {arc_power:.01f}%, Tc {tc_kelvin_mirek(tc)}K{tc_detailed_info}")

async def main():
d = tridonic("/dev/dali/daliusb-*", glob=True)
# d = tridonic("/dev/dali/daliusb-*", glob=True)
d = hasseb("/dev/dali/hasseb-*", glob=True)

if len(sys.argv) < 2:
show_usage()
Expand Down