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

Unbound Backlight Brightness; Turn Backlight Off at 0x00 #299

Merged
merged 7 commits into from
Jan 3, 2021
Merged
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
8 changes: 6 additions & 2 deletions docs/mqtt.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,12 @@ The MQTT format of the command is:

Switch, KeypadLinc, and Dimmer all support the flags:

- backlight: integer in the range 0x00-0xff which changes the LED backlight
level on the device.
- backlight: changes the LED backlight level on the device. Insteon
documentation defines the range from 0x11 - 0x7F, however, levels below
0x11 appear to work on some devices, and levels above 0x7F may also work.
Switches and dimmers go as low as 0x04 and KeypadLincs go all the way
down to 0x01. Setting to 0x00 will turn off the backlight, any other
value will turn on the backlight.
- on_level: integer in the range 0x00-0xff which sets the on level that will
be used when the button is pressed.
- load_attached: 0/1 to attach or detach the load from the group 1 button.
Expand Down
1 change: 0 additions & 1 deletion insteon_mqtt/db/Device.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import io
import itertools
import json
import os
from ..Address import Address
from .. import catalog
from ..CommandSeq import CommandSeq
Expand Down
1 change: 0 additions & 1 deletion insteon_mqtt/db/Modem.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#===========================================================================
import io
import json
import os
from ..Address import Address
from .. import handler
from .. import log
Expand Down
49 changes: 30 additions & 19 deletions insteon_mqtt/device/Dimmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,31 +439,42 @@ def set_backlight(self, level, on_done=None):

The default factory level is 0x1f.

Per page 157 of insteon dev guide range is between 0x11 and 0x7F,
however in practice backlight can be incremented from 0x00 to at least
0x7f.

Args:
level (int): The backlight level in the range [0,255]
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
"""
LOG.info("Dimmer %s setting backlight to %s", self.label, level)

# Bound to 0x11 <= level <= 0xff per page 157 of insteon dev guide.
# 0x00 is used to disable the backlight so allow that explicitly.
if level:
level = max(0x11, min(level, 0xff))

# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x07, # D2 set global led brightness
level, # D3 brightness level
] + [0x00] * 11)

msg = Msg.OutExtended.direct(self.addr, 0x2e, 0x00, data)

# Use the standard command handler which will notify us when the
# command is ACK'ed.
seq = CommandSeq(self, "Dimmer set backlight complete", on_done,
name="SetBacklight")

# First set the backlight on or off depending on level value
is_on = level > 0
LOG.info("Dimmer %s setting backlight to %s", self.label, is_on)
cmd = 0x09 if is_on else 0x08
msg = Msg.OutExtended.direct(self.addr, 0x20, cmd, bytes([0x00] * 14))
msg_handler = handler.StandardCmd(msg, self.handle_backlight, on_done)
self.send(msg, msg_handler)
seq.add_msg(msg, msg_handler)

if is_on:
# Second set the level only if on
LOG.info("Dimmer %s setting backlight to %s", self.label, level)
# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x07, # D2 set global led brightness
level, # D3 brightness level
] + [0x00] * 11)

msg = Msg.OutExtended.direct(self.addr, 0x2e, 0x00, data)
msg_handler = handler.StandardCmd(msg, self.handle_backlight,
on_done)
seq.add_msg(msg, msg_handler)

seq.run()

#-----------------------------------------------------------------------
def get_flags(self, on_done=None):
Expand Down
72 changes: 19 additions & 53 deletions insteon_mqtt/device/KeypadLinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,6 @@ def __init__(self, protocol, modem, address, name, dimmer=True):
# Button 1 level (0-255)
self._level = 0

# Backlight on/off. See set_backlight for details.
self._backlight = True

# Update the group map with the groups to be paired and the handler
# for broadcast messages from this group
# We don't configure for the distinction between 6 and 8 keypads
Expand Down Expand Up @@ -753,27 +750,32 @@ def set_backlight(self, level, on_done=None):

The default factory level is 0x1f.

Per page 157 of insteon dev guide range is between 0x11 and 0x7F,
however in practice backlight can be incremented from 0x00 to at least
0x7f.

Args:
level (int): The backlight level in the range [0,255]
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
"""
LOG.info("KeypadLinc %s setting backlight to %s", self.label, level)

# If the input level is zero, turn off the backlight. That's a
# different command than setting the level. We also need to keep
# track of it being off because we need to turn it back on before
# setting the level if that level is changed in the future.
if not level:
self.set_backlight_on(False, on_done)
seq = CommandSeq(self, "KeypadLinc set backlight complete", on_done,
name="SetBacklight")

# Otherwise use the level changing command.
else:
seq = CommandSeq(self, "Backlight level", on_done,
name="SetBacklight")
# First set the backlight on or off depending on level value
is_on = level > 0
LOG.info("KeypadLinc %s setting backlight to %s", self.label, is_on)
cmd = 0x09 if is_on else 0x08
msg = Msg.OutExtended.direct(self.addr, 0x20, cmd, bytes([0x00] * 14))
callback = functools.partial(self.handle_ack, task="Backlight on")
msg_handler = handler.StandardCmd(msg, callback, on_done)
seq.add_msg(msg, msg_handler)

# Bound to 0x11 <= level <= 0x7f per page 157 of insteon dev guide.
level = max(0x11, min(level, 0x7f))
if is_on:
# Second set the level only if on
LOG.info("KeypadLinc %s setting backlight to %s", self.label,
level)

# Extended message data - see Insteon dev guide p156.
data = bytes([
Expand All @@ -790,29 +792,7 @@ def set_backlight(self, level, on_done=None):
msg_handler = handler.StandardCmd(msg, callback, on_done)
seq.add_msg(msg, msg_handler)

# If the backlight was off, turn it back on.
if not self._backlight:
seq.add(self.set_backlight_on, True)

seq.run()

#-----------------------------------------------------------------------
def set_backlight_on(self, is_on, on_done=None):
"""Turn the backlight on or totally off.

Args:
is_on (bool): True to have the backlight on, False for off.
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
"""
LOG.info("KeypadLinc %s setting backlight to %s", self.label, is_on)
cmd = 0x09 if is_on else 0x08
msg = Msg.OutExtended.direct(self.addr, 0x20, cmd, bytes([0x00] * 14))

# This callback changes self._backlight if the command works.
callback = functools.partial(self.handle_backlight_on, is_on=is_on)
msg_handler = handler.StandardCmd(msg, callback, on_done)
self.send(msg, msg_handler)
seq.run()

#-----------------------------------------------------------------------
def get_flags(self, on_done=None):
Expand Down Expand Up @@ -1272,20 +1252,6 @@ def get_on_level(self):
on_level = 0xff
return on_level

#-----------------------------------------------------------------------
def handle_backlight_on(self, msg, on_done, is_on):
"""Callback for handling turning the backlight on and off.

Args:
msg (InpStandard): The response message from the command.
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
is_on (bool): True if the backlight is being turned on, False for
off.
"""
on_done(True, "backlight set to %s" % is_on, None)
self._backlight = is_on

#-----------------------------------------------------------------------
def handle_refresh(self, msg):
"""Handle replies to the refresh command.
Expand Down
45 changes: 28 additions & 17 deletions insteon_mqtt/device/Outlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,32 +317,43 @@ def set_backlight(self, level, on_done=None):

The default factory level is 0x1f.

Per page 157 of insteon dev guide range is between 0x11 and 0x7F,
however in practice backlight can be incremented from 0x00 to at least
0x7f.

Args:
level (int): The backlight level in the range [0,255]
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
"""
LOG.info("Outlet %s setting backlight to %s", self.label, level)
seq = CommandSeq(self, "Outlet set backlight complete", on_done,
name="SetBacklight")

# First set the backlight on or off depending on level value
is_on = level > 0
LOG.info("Outlet %s setting backlight to %s", self.label, is_on)
cmd = 0x09 if is_on else 0x08
msg = Msg.OutExtended.direct(self.addr, 0x20, cmd, bytes([0x00] * 14))
msg_handler = handler.StandardCmd(msg, self.handle_backlight, on_done)
seq.add_msg(msg, msg_handler)

# Bound to 0x11 <= level <= 0xff per page 157 of insteon dev guide.
# 0x00 is used to disable the backlight so allow that explicitly.
if level:
level = max(0x11, min(level, 0xff))
if is_on:
# Second set the level only if on
LOG.info("Outlet %s setting backlight to %s", self.label, level)

# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x07, # D2 set global led brightness
level, # D3 brightness level
] + [0x00] * 11)
# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x07, # D2 set global led brightness
level, # D3 brightness level
] + [0x00] * 11)

msg = Msg.OutExtended.direct(self.addr, 0x2e, 0x00, data)
msg = Msg.OutExtended.direct(self.addr, 0x2e, 0x00, data)
msg_handler = handler.StandardCmd(msg, self.handle_backlight,
on_done)
seq.add_msg(msg, msg_handler)

# Use the standard command handler which will notify us when the
# command is ACK'ed.
msg_handler = handler.StandardCmd(msg, self.handle_backlight, on_done)

self.send(msg, msg_handler)
seq.run()

#-----------------------------------------------------------------------
def set_flags(self, on_done, **kwargs):
Expand Down
45 changes: 28 additions & 17 deletions insteon_mqtt/device/Switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,32 +250,43 @@ def set_backlight(self, level, on_done=None):

The default factory level is 0x1f.

Per page 157 of insteon dev guide range is between 0x11 and 0x7F,
however in practice backlight can be incremented from 0x00 to at least
0x7f.

Args:
level (int): The backlight level in the range [0,255]
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
"""
LOG.info("Switch %s setting backlight to %s", self.label, level)

# Bound to 0x11 <= level <= 0xff per page 157 of insteon dev guide.
# 0x00 is used to disable the backlight so allow that explicitly.
if level:
level = max(0x11, min(level, 0xff))
seq = CommandSeq(self, "Switch set backlight complete", on_done,
name="SetBacklight")

# First set the backlight on or off depending on level value
is_on = level > 0
LOG.info("Switch %s setting backlight to %s", self.label, is_on)
cmd = 0x09 if is_on else 0x08
msg = Msg.OutExtended.direct(self.addr, 0x20, cmd, bytes([0x00] * 14))
msg_handler = handler.StandardCmd(msg, self.handle_backlight, on_done)
seq.add_msg(msg, msg_handler)

# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x07, # D2 set global led brightness
level, # D3 brightness level
] + [0x00] * 11)
if is_on:
# Second set the level only if on
LOG.info("Switch %s setting backlight to %s", self.label, level)

msg = Msg.OutExtended.direct(self.addr, 0x2e, 0x00, data)
# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x07, # D2 set global led brightness
level, # D3 brightness level
] + [0x00] * 11)

# Use the standard command handler which will notify us when the
# command is ACK'ed.
msg_handler = handler.StandardCmd(msg, self.handle_backlight, on_done)
msg = Msg.OutExtended.direct(self.addr, 0x2e, 0x00, data)
msg_handler = handler.StandardCmd(msg, self.handle_backlight,
on_done)
seq.add_msg(msg, msg_handler)

self.send(msg, msg_handler)
seq.run()

#-----------------------------------------------------------------------
def set_flags(self, on_done, **kwargs):
Expand Down
38 changes: 38 additions & 0 deletions tests/device/test_DimmerDev.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,41 @@ def on_done(success, *args):
assert success
test_device.handle_ext_flags(msg, on_done)
assert test_device.get_on_level() == 0x1C

def test_set_backlight(self, test_device):
# set_backlight(self, level, on_done=None)
test_device.set_backlight(0)
assert len(test_device.protocol.sent) == 1
assert test_device.protocol.sent[0].msg.cmd1 == 0x20
assert test_device.protocol.sent[0].msg.cmd2 == 0x08
test_device.protocol.clear()

def level_bytes(level):
data = bytes([
0x01, # D1 must be group 0x01
0x07, # D2 set global led brightness
level, # D3 brightness level
] + [0x00] * 11)
return data

for params in ([1, 0x01], [255, 0xFF], [127, 127]):
with mock.patch.object(IM.CommandSeq, 'add_msg'):
test_device.set_backlight(params[0])
args_list = IM.CommandSeq.add_msg.call_args_list
assert IM.CommandSeq.add_msg.call_count == 2
# Check the first call
assert args_list[0][0][0].cmd1 == 0x20
assert args_list[0][0][0].cmd2 == 0x09
# Check the first call
assert args_list[1][0][0].cmd1 == 0x2e
assert args_list[1][0][0].data == level_bytes(params[1])


with mock.patch.object(IM.CommandSeq, 'add_msg'):
# test backlight off
test_device.set_backlight(0)
args_list = IM.CommandSeq.add_msg.call_args_list
assert IM.CommandSeq.add_msg.call_count == 1
# Check the first call
assert args_list[0][0][0].cmd1 == 0x20
assert args_list[0][0][0].cmd2 == 0x08
Loading