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

Support for the default ramp rate setting for Dimmer and KeypadLinc #235

Merged
merged 8 commits into from
Dec 14, 2020
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
5 changes: 5 additions & 0 deletions docs/mqtt.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@ Switch, KeypadLinc, and Dimmer all support the flags:
signal_bits input. If a bit is 0, then that button is a toggle button
and will alternate on an doff signals

KeypadLinc and Dimmer support the flags:

- ramp_rate: float in the range of 0.5 to 540 seconds which sets the default ramp
rate that will be used when the button is pressed

IOLinc supports the flags:

- mode: "latching" / "momentary-a" / "momentary-b" / "momentary-c" to
Expand Down
62 changes: 60 additions & 2 deletions insteon_mqtt/device/Dimmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Dimmer(Base):

def __init__(self, protocol, modem, address, name=None):
"""Constructor

Args:
protocol (Protocol): The Protocol object used to communicate
with the Insteon network. This is needed to allow the
Expand Down Expand Up @@ -521,6 +521,41 @@ def set_on_level(self, level, on_done=None):
msg_handler = handler.StandardCmd(msg, self.handle_on_level, on_done)
self.send(msg, msg_handler)

#-----------------------------------------------------------------------
def set_ramp_rate(self, rate, on_done=None):
"""Set the device default ramp rate.

This changes the dimmer default ramp rate of how quickly it will
turn on or off. This rate can be between 0.1 seconds and up to 9
minutes.

Args:
rate (float): Ramp rate in in the range [0.1, 540] seconds
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
"""
LOG.info("Dimmer %s setting ramp rate to %s", self.label, rate)

data_3 = 0x1c #the default ramp rate is .5
for ramp_key, ramp_value in self.ramp_pretty.items():
if rate >= ramp_value:
data_3 = ramp_key
break

# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x05, # D2 set ramp rate when button is pressed
data_3, # D3 rate
] + [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.
msg_handler = handler.StandardCmd(msg, self.handle_ramp_rate, on_done)
self.send(msg, msg_handler)

#-----------------------------------------------------------------------
def set_flags(self, on_done, **kwargs):
"""Set internal device flags.
Expand All @@ -545,7 +580,8 @@ def set_flags(self, on_done, **kwargs):
# passed in.
FLAG_BACKLIGHT = "backlight"
FLAG_ON_LEVEL = "on_level"
flags = set([FLAG_BACKLIGHT, FLAG_ON_LEVEL])
FLAG_RAMP_RATE = "ramp_rate"
flags = set([FLAG_BACKLIGHT, FLAG_ON_LEVEL, FLAG_RAMP_RATE])
unknown = set(kwargs.keys()).difference(flags)
if unknown:
raise Exception("Unknown Dimmer flags input: %s.\n Valid flags "
Expand All @@ -562,6 +598,10 @@ def set_flags(self, on_done, **kwargs):
on_level = util.input_byte(kwargs, FLAG_ON_LEVEL)
seq.add(self.set_on_level, on_level)

if FLAG_RAMP_RATE in kwargs:
rate = util.input_float(kwargs, FLAG_RAMP_RATE)
seq.add(self.set_ramp_rate, rate)

seq.run()

#-----------------------------------------------------------------------
Expand Down Expand Up @@ -600,6 +640,24 @@ def handle_on_level(self, msg, on_done):
else:
on_done(False, "Button on level failed", None)

#-----------------------------------------------------------------------
def handle_ramp_rate(self, msg, on_done):
"""Callback for handling set_ramp_rate() responses.

This is called when we get a response to the set_ramp_rate() command.
We don't need to do anything - just call the on_done callback with
the status.

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)
"""
if msg.flags.type == Msg.Flags.Type.DIRECT_ACK:
on_done(True, "Button ramp rate updated", None)
else:
on_done(False, "Button ramp rate failed", None)

#-----------------------------------------------------------------------
def handle_broadcast(self, msg):
"""Handle broadcast messages from this device.
Expand Down
48 changes: 47 additions & 1 deletion insteon_mqtt/device/KeypadLinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,47 @@ def set_on_level(self, level, on_done=None):

self.send(msg, msg_handler)

#-----------------------------------------------------------------------
def set_ramp_rate(self, rate, on_done=None):
"""Set the device default ramp rate.

This changes the dimmer default ramp rate of how quickly the it
will turn on or off. This rate can be between 0.1 seconds and up
to 9 minutes.

Args:
rate (float): Ramp rate in in the range [0.1, 540] seconds
on_done: Finished callback. This is called when the command has
completed. Signature is: on_done(success, msg, data)
"""
if not self.is_dimmer:
LOG.error("KeypadLinc %s switch doesn't support setting ramp_rate",
self.addr)
return

LOG.info("Dimmer %s setting ramp rate to %s", self.label, rate)

data_3 = 0x1c #the default ramp rate is .5
for ramp_key, ramp_value in Dimmer.ramp_pretty.items():
if rate >= ramp_value:
data_3 = ramp_key
break

# Extended message data - see Insteon dev guide p156.
data = bytes([
0x01, # D1 must be group 0x01
0x05, # D2 set ramp rate when button is pressed
data_3, # D3 rate
] + [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.
callback = functools.partial(self.handle_ack, task="Button ramp rate")
msg_handler = handler.StandardCmd(msg, callback, on_done)
self.send(msg, msg_handler)

#-----------------------------------------------------------------------
def set_flags(self, on_done, **kwargs):
"""Set internal device flags.
Expand All @@ -895,14 +936,15 @@ def set_flags(self, on_done, **kwargs):
FLAG_BACKLIGHT = "backlight"
FLAG_GROUP = "group"
FLAG_ON_LEVEL = "on_level"
FLAG_RAMP_RATE = "ramp_rate"
FLAG_LOAD_ATTACH = "load_attached"
FLAG_FOLLOW_MASK = "follow_mask"
FLAG_OFF_MASK = "off_mask"
FLAG_SIGNAL_BITS = "signal_bits"
FLAG_NONTOGGLE_BITS = "nontoggle_bits"
flags = set([FLAG_BACKLIGHT, FLAG_LOAD_ATTACH, FLAG_FOLLOW_MASK,
FLAG_SIGNAL_BITS, FLAG_NONTOGGLE_BITS, FLAG_OFF_MASK,
FLAG_GROUP, FLAG_ON_LEVEL])
FLAG_GROUP, FLAG_ON_LEVEL, FLAG_RAMP_RATE])
unknown = set(kwargs.keys()).difference(flags)
if unknown:
raise Exception("Unknown KeypadLinc flags input: %s.\n Valid "
Expand All @@ -927,6 +969,10 @@ def set_flags(self, on_done, **kwargs):
on_level = util.input_byte(kwargs, FLAG_ON_LEVEL)
seq.add(self.set_on_level, on_level)

if FLAG_RAMP_RATE in kwargs:
rate = util.input_float(kwargs,FLAG_RAMP_RATE)
seq.add(self.set_ramp_rate, rate)

if FLAG_FOLLOW_MASK in kwargs:
if group is None:
raise Exception("follow_mask requires group=<NUM> to be input")
Expand Down
32 changes: 32 additions & 0 deletions insteon_mqtt/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,35 @@ def input_byte(inputs, field):
except ValueError:
msg = "Invalid %s input. Valid inputs are 0-255" % input
raise ValueError(msg)

#===========================================================================
def input_float(inputs, field):
"""Convert an input field to an float.

Raises:
If the input is not a valid float, an exception is thrown.

Args:
inputs (dict): Key/value pairs of user inputs.
field (str): The field to get.

Returns:
Returns None if field is not in inputs. Otherwise the input field
is converted to an integer and returned.
"""
value = inputs.pop(field, None)
if value is None:
return None

try:
if isinstance(value, str):
lv = value.lower()
if lv == 'none':
return None
else:
return float(value)
else:
return float(value)
except ValueError:
msg = "Invalid %s input. Valid inputs are float values." % input
raise ValueError(msg)