Skip to content

Commit

Permalink
Modified RemoteLinc to look like mini_remote, but with modified batte…
Browse files Browse the repository at this point in the history
…ry_low setting and 6 buttons
  • Loading branch information
f1d094 committed Mar 19, 2024
1 parent e2ff4b4 commit b148006
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 14 deletions.
2 changes: 1 addition & 1 deletion insteon_mqtt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
'mini_remote4' : (device.Remote, {'num_button' : 4}),
'mini_remote6' : (device.Remote, {'num_button' : 6}),
'mini_remote8' : (device.Remote, {'num_button' : 8}),
'remote_linc' : (device.RemoteLinc, {'num_button' : 6}),
'remote_linc' : (device.RemoteLinc, {}),
'motion' : (device.Motion, {}),
'outlet' : (device.Outlet, {}),
'smoke_bridge' : (device.SmokeBridge, {}),
Expand Down
114 changes: 111 additions & 3 deletions insteon_mqtt/device/RemoteLinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
#
#===========================================================================
import time
from .functions import ManualCtrl
from .BatterySensor import BatterySensor
from .functions import ManualCtrl
from .. import log
from .. import message as Msg
from .. import handler
Expand All @@ -15,7 +15,8 @@


class RemoteLinc(ManualCtrl, BatterySensor):
"""Insteon RemoteLinc (2440) 6-button, RF, remote control device
"""
Insteon RemoteLinc (2440) 6-button, RF, remote control device
RemoteLincs don't have battery status and have 6 buttons. Otherwise they are
similar to mini remotes. This was copied from the mini remote and then
Expand All @@ -34,10 +35,20 @@ class RemoteLinc(ManualCtrl, BatterySensor):
State changes are communicated by emitting signals. Other classes can
connect to these signals to perform an action when a change is made to
the device (like sending MQTT messages).
"""
# This defines what is the minimum time between battery status requests
# for devices that support it. Value is in seconds
# Currently set at 4 Days
BATTERY_TIME = (60 * 60) * 24 * 4

# Voltages below this value will report as low
# Anecdotal testing shows that for 4xAAA batteries, 4.4v (1.1v per cell)
# is the low point.
BATTERY_VOLTAGE_LOW = 4.4

def __init__(self, protocol, modem, address, name, config_extra,
num_button):
num_button = 6):
"""Constructor
Args:
Expand All @@ -62,6 +73,58 @@ def __init__(self, protocol, modem, address, name, config_extra,
for i in range(1, self.num + 1):
self.group_map[i] = self.handle_on_off

self.cmd_map.update({
'get_battery_voltage' : self.get_extended_flags,
})

# This allows for a short timer between sending automatic battery
# requests. Otherwise, a request may get queued multiple times
self._battery_request_time = 0

#-----------------------------------------------------------------------
@property
def battery_voltage_time(self):
"""Returns the timestamp of the last battery voltage report from the
saved metadata
"""
meta = self.db.get_meta('RemoteLinc')
ret = 0
if isinstance(meta, dict) and 'battery_voltage_time' in meta:
ret = meta['battery_voltage_time']
return ret

#-----------------------------------------------------------------------
@battery_voltage_time.setter
def battery_voltage_time(self, val):
"""Saves the timestamp of the last battery voltage report to the
database metadata
Args:
val: (timestamp) time.time() value
"""
meta = {'battery_voltage_time': val}
existing = self.db.get_meta('RemoteLinc')
if isinstance(existing, dict):
existing.update(meta)
self.db.set_meta('RemoteLinc', existing)
else:
self.db.set_meta('RemoteLinc', meta)

#-----------------------------------------------------------------------
def handle_extended_flags(self, msg, on_done):
"""Receives the extended flags payload from the device
Primarily this is used to get the battery voltage
"""
# D10 voltage in tenth position, but remember starts at 0
batt_volt = msg.data[9] / 50
LOG.info("RemoteLinc %s battery voltage is %s", self.label,
batt_volt)
self.battery_voltage_time = time.time()
# Signal low battery
self.signal_low_battery.emit(self,
batt_volt <= self.BATTERY_VOLTAGE_LOW)
on_done(True, "Battery voltage is %s" % batt_volt, msg.data[9])

#-----------------------------------------------------------------------
def link_data(self, is_controller, group, data=None):
"""Create default device 3 byte link data.
Expand Down Expand Up @@ -103,12 +166,57 @@ def link_data(self, is_controller, group, data=None):
# For each field, use the input if not -1, else the default.
return util.resolve_data3(defaults, data)

#-----------------------------------------------------------------------
def get_extended_flags(self, on_done):
"""Requests the Extended Flags from the Device
Notably, these flags contain the battery voltage.
"""
data = bytes([0x01] + [0x00] * 13)
msg = Msg.OutExtended.direct(self.addr, 0x2e, 0x00, data)
msg_handler = handler.ExtendedCmdResponse(msg,
self.handle_extended_flags,
on_done=on_done)
self.send(msg, msg_handler)

#-----------------------------------------------------------------------
def auto_check_battery(self):
"""Queues a Battery Voltage Request if Necessary
If the device supports it, and the requisite amount of time has
elapsed, queue a battery request.
"""
if (self.db.desc is not None and
self.db.desc.model.split("-")[0] == "2342"):
# This is a device that supports battery requests
last_checked = self.battery_voltage_time
# Don't send this message more than once every 5 minutes no
# matter what
if (last_checked + self.BATTERY_TIME <= time.time() and
self._battery_request_time + 300 <= time.time()):
self._battery_request_time = time.time()
LOG.info("RemoteLinc %s: Auto requesting battery voltage",
self.label)
self.get_extended_flags(None)

#-----------------------------------------------------------------------
def awake(self, on_done):
"""Injects a Battery Voltage Request if Necessary
Queue a battery request that should go out now, since the device is
awake.
"""
self.auto_check_battery()
super().awake(on_done)

#-----------------------------------------------------------------------
def _pop_send_queue(self):
"""Injects a Battery Voltage Request if Necessary
Queue a battery request that should go out now, since the device is
awake.
"""
self.auto_check_battery()
super()._pop_send_queue()

#-----------------------------------------------------------------------
22 changes: 12 additions & 10 deletions insteon_mqtt/mqtt/RemoteLinc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#===========================================================================
#
# MQTT RemoteLinc (2440) battery powered remote control.
# MQTT RemoteLinc (2440) remote control interface.
#
#===========================================================================
from .. import log
Expand All @@ -10,15 +10,11 @@
LOG = log.get_logger()


class RemoteLinc(BatterySensor, topic.ManualTopic):
"""MQTT interface to an Insteon RemoteLinc (2440) remote control.
RemoteLincs don't have battery status and have 6 buttons. Otherwise they are
identical to mini remotes. This MQTT and device definitions are copied from
the mini remote and then modified to handle the 6 buttons and removed the
battery status
class Remote(BatterySensor, topic.ManualTopic):
"""
MQTT interface to an Insteon RemoteLinc (2440) remote control.
This class connects to a device.RemoteLinc object and converts it's output
This class connects to a device.Remote object and converts it's output
state changes to MQTT messages.
Remotes report button presses on the remote control.
Expand All @@ -28,7 +24,7 @@ def __init__(self, mqtt, device):
Args:
mqtt (mqtt.Mqtt): The MQTT main interface.
device (device.RemoteLinc): The Insteon object to link to.
device (device.Remote): The Insteon object to link to.
"""
super().__init__(mqtt, device,
state_topic='insteon/{{address}}/state/{{button}}',
Expand Down Expand Up @@ -70,4 +66,10 @@ def load_config(self, config, qos=None):
self.load_state_data(data, qos)
self.load_manual_data(data, qos)

# Leak and Motion allow for overrides b/c of grandfathering. But I
# think is may be a helpful feature, so enabling here too.
if "low_battery_topic" in data:
self.msg_battery.load_config(data, 'low_battery_topic',
'low_battery_payload', qos)

#-----------------------------------------------------------------------

0 comments on commit b148006

Please sign in to comment.