Skip to content

Commit

Permalink
Added new linking command to the device and modem. Simulates pressing
Browse files Browse the repository at this point in the history
the set button for 3 sec and allows pairing of new devices.  Fixes #7.
Also updated the db modem delete commands so individual entries can be
removed and added a 3 byte data input and better defaults for those values
when creating device db links.
  • Loading branch information
TD22057 committed Dec 24, 2017
1 parent 039cb53 commit 58cd68c
Show file tree
Hide file tree
Showing 16 changed files with 380 additions and 288 deletions.
58 changes: 21 additions & 37 deletions doc/mqtt.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ brackets [], then it's optional. If a value can be one of many like
true or false, then the possible values are separated by a slash /.

The MQTT topic to publish managemeng commands to is (aa.bb.cc is the
device address or name):
device address or nice name from the config.yaml file):

```
insteon/command/aa.bb.cc
Expand All @@ -62,15 +62,28 @@ device address or name):

### Activate all linking mode

Supported: modem
Supported: modem, devices

This turns on all linking mode and is the same as pressing the set
button on the modem. The command payload is:
button for 3 sec on the modem or device. The default group is 1.

This can be used to connect new devices to the modem. Run 'linking modem',
'linking aa.bb.cc' to control the device from the modem and 'linking
aa.bb.cc', 'linking modem' so the modem will get broadcast messages from the
device. For more complicated devices, then run a 'pair device' command to
configure the other links that the device needs.

The command payload is:

```
{ "cmd" : "set_btn", ["timeout" : time_sec] }
{ "cmd" : "linking", ["group" : group] }
```

Once you run the linking command, you should also run 'refresh' on the device
to update it's local database. The modem will automatically update, but the
devices don't send a message when the linking is complete so there is no way
to know when to update the database.


### Refresh the device state and download it's all link database

Expand Down Expand Up @@ -114,7 +127,7 @@ command payload is:

```
{ "cmd" : "db_add_ctrl_of", "addr" : aa.bb.cc, "group" : group,
["two_way" : True/False] }
["two_way" : True/False], ["data" : [D1, D2,D3]] }
```

### Add the device as a responder of another device.
Expand All @@ -130,13 +143,13 @@ command payload is:

```
{ "cmd" : "db_add_resp_of", "addr" : aa.bb.cc, "group" : group,
["two_way" : True/False] }
["two_way" : True/False], ["data" : [D1, D2,D3]] } }
```


### Delete the device as a controller of another device.

Supported: devices
Supported: modem, devices

This commands modifies the all link database on the device to remove
it as a controller of another device. If the two-way flag is set
Expand All @@ -150,14 +163,10 @@ The command payload is:
["two_way" : True/False] }
```

NOTE: The modem doesn't support removal of specific links by type.
The modem can only remove all the links for a given address and group
(see below).


### Delete the device as a responder of another device.

Supported: devices
Supported: modem, devices

This commands modifies the all link database on the device to remove
it as a responder of another device. If the two-way flag is set (True
Expand All @@ -171,31 +180,6 @@ The command payload is:
["two_way" : True/False] }
```

NOTE: The modem doesn't support removal of specific links by type.
The modem can only remove all the links for a given address and group
(see below).


### Delete a device and group from the modem all link database.

Supported: modem

THis command modifies the modem's all link database to remove both the
controller and responder records for an address and group. If the
two-way flag is set (True is the default), it will also remove the
corresponding link(s) on the remote device as well.

```
{ "cmd" : "db_delete", "addr" : aa.bb.cc, "group" : group,
["two_way" : True/False] }
```

NOTE: A future enhancement is to make the modem code smarter to handle
specific link removal. Currenly the modem just removes the first link
it finds (controller or responder). So a future version could track
that and remove links until the requested link is removed, then add
back the links that sholdn't have been removed in the first place.

---

# State change commands
Expand Down
52 changes: 42 additions & 10 deletions insteon_mqtt/Modem.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,17 @@ def __init__(self, protocol):
'db_del_resp_of' : self.db_del_resp_of,
'refresh' : self.refresh,
'refresh_all' : self.refresh_all,
'set_btn' : self.set_btn,
'linking' : self.linking,
}

# Add a generic read handler for any broadcast messages
# initiated by the Insteon devices.
self.protocol.add_handler(handler.Broadcast(self))

# Handle all link complete messages that the modem sends when the set
# button or linking mode is finished.
self.protocol.add_handler(handler.ModemLinkComplete(self))

# Handle user triggered factory reset of the modem.
self.protocol.add_handler(handler.ModemReset(self))

Expand Down Expand Up @@ -389,21 +393,37 @@ def factory_reset(self):
"""TODO: doc
"""
LOG.warning("Modem being reset. All data will be lost")
msg = Msg.OutResetPlm()
msg = Msg.OutResetModem()
msg_handler = handler.ModemReset(self)
self.protocol.send(msg, msg_handler)

#-----------------------------------------------------------------------
def set_btn(self, group=0x01, time_out=60, on_done=None):
def linking(self, group=0x01, on_done=None):
"""TODO: doc
"""
# Tell the modem to enter all link mode for the group. The
# handler will handle timeouts (to send the cancel message) if
# nothing happens. See the handler for details.
msg = Msg.OutAllLinkStart(Msg.OutAllLinkStart.Cmd.EITHER, group)
msg_handler = handler.ModemAllLink(self, time_out, on_done)
msg = Msg.OutModemLinking(Msg.OutModemLinking.Cmd.EITHER, group)
msg_handler = handler.ModemLinkStart(on_done)
self.protocol.send(msg, msg_handler)

#-----------------------------------------------------------------------
def link_data(self, group, is_controller):
"""TODO: doc
"""
# Normally, the modem (ctrl) -> device (resp) link is created using
# the linking() command - then the handler.ModemLinkComplete will
# fill these values in for us using the device information. But they
# probably aren't used so it doesn't really matter.
if is_controller:
return bytes([group, 0x00, 0x00])

# Responder data is a mystery on the modem. This seems to work but
# it's unclear if it's needed at all.
else:
return bytes([group, 0x00, 0x00])

#-----------------------------------------------------------------------
def run_scene(self, group, is_on, cmd1=None, cmd2=0x00):
"""TODO: doc
Expand All @@ -412,7 +432,7 @@ def run_scene(self, group, is_on, cmd1=None, cmd2=0x00):
cmd1 = 0x11 if is_on else 0x13

# TODO: why doesn't this work?
msg = Msg.OutPlmScene(group, cmd1, cmd2)
#msg = Msg.OutModemScene(group, cmd1, cmd2)
#msg_handler = handler.

#-----------------------------------------------------------------------
Expand Down Expand Up @@ -574,6 +594,13 @@ def _db_update(self, addr, group, data, two_way, is_controller, on_done):
if two_way and remote:
use_cb = functools.partial(self._db_update_remote, remote, on_done)

# Get the data array to use. See Github issue #7 for discussion.
# Use teh bytes() cast here so we can take a list as input.
if data is None:
data = self.link_data(group, is_controller)
else:
data = bytes(data)

# Create a new database entry for the modem and send it to the
# modem for updating.
entry = db.ModemEntry(addr, group, is_controller, data)
Expand All @@ -593,14 +620,19 @@ def _db_update_remote(self, remote, on_done, success, msg, entry):
on_done(False, msg, entry)
return

# Send the command to the device. Two way is false here since
# we just added the other link.
# Send the command to the device. Two way is false here since we
# just added the other link. Data is always None since we don't know
# what the remote device data should be and it never should be the
# same as our link (ctrl vs resp are always different). So this
# forces it to use self.link_data() to get a reasonable data set to
# use in the link.
two_way = False
data = None
if entry.is_controller:
remote.db_add_resp_of(self.addr, entry.group, entry.data, two_way,
remote.db_add_resp_of(self.addr, entry.group, data, two_way,
on_done)
else:
remote.db_add_ctrl_of(self.addr, entry.group, entry.data, two_way,
remote.db_add_ctrl_of(self.addr, entry.group, data, two_way,
on_done)

#-----------------------------------------------------------------------
Expand Down
48 changes: 30 additions & 18 deletions insteon_mqtt/cmd_line/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
from . import util


#===========================================================================
def linking(args, config):
topic = "%s/%s" % (args.topic, args.address)
payload = {
"cmd" : "linking",
"group" : args.group,
}

reply = util.send(config, topic, payload, args.quiet)
return reply["result"]


#===========================================================================
def on(args, config):
topic = "%s/%s" % (args.topic, args.address)
Expand Down Expand Up @@ -109,24 +121,24 @@ def db_add(args, config):
"'addr1 -> addr2'." % args.link)

# Use strings for the default - the parser below converts to int.
if args.data is None:
args.data = ["0", "0", "0"]
elif len(args.data) > 3:
raise ValueError("Input data field %s should be 0-3 integer values."
% args.data)

# Pad the data inputs out to 3 elements and convert any strings to
# integers.
data = []
for i in range(3):
value = 0
if i < len(args.data):
if "0x" in args.data[i]:
value = int(args.data[i], 16)
else:
value = int(args.data[i])

data.append(value)
data = None
if args.data:
if len(args.data) > 3:
raise ValueError("Input data field %s should be 0-3 integer "
"values." % args.data)

# Pad the data inputs out to 3 elements and convert any strings to
# integers.
data = []
for i in range(3):
value = 0
if i < len(args.data):
if "0x" in args.data[i]:
value = int(args.data[i], 16)
else:
value = int(args.data[i])

data.append(value)

topic = "%s/%s" % (args.topic, address1)
payload = {
Expand Down
33 changes: 14 additions & 19 deletions insteon_mqtt/cmd_line/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,6 @@ def parse_args(args):
"file to use.")
sp.set_defaults(func=start.start)

#---------------------------------------
# modem.set command
sp = sub.add_parser("link", help="Turn on modem linking. This is the "
"same as pressing the modem set button.")
sp.add_argument("-w", "--timeout", type=int, metavar="timeout",
default=30, help="Time out in seconds to end linking.")
sp.add_argument("-q", "--quiet", action="store_true",
help="Don't print any command results to the screen.")
sp.add_argument("config", metavar="config.yaml", help="Configuration "
"file to use.")
sp.set_defaults(func=modem.set_btn)

#---------------------------------------
# modem.refresh_all command
sp = sub.add_parser("refresh-all", help="Call refresh all on the devices "
Expand All @@ -57,6 +45,20 @@ def parse_args(args):
"file to use.")
sp.set_defaults(func=modem.refresh_all)

#---------------------------------------
# device.linking command
sp = sub.add_parser("linking", help="Turn on device or modem linking. "
"This is the same as holding the modem set button "
"for 3 seconds.")
sp.add_argument("-g", "--group", type=int, default=0x01,
help="Group number to link with (1-255)")
sp.add_argument("-q", "--quiet", action="store_true",
help="Don't print any command results to the screen.")
sp.add_argument("config", metavar="config.yaml", help="Configuration "
"file to use.")
sp.add_argument("address", help="Device address or name.")
sp.set_defaults(func=device.linking)

#---------------------------------------
# device.refresh command
sp = sub.add_parser("refresh", help="Refresh device/modem state and "
Expand Down Expand Up @@ -198,13 +200,6 @@ def parse_args(args):
help="Controller or responder flag of the entry to remove")
sp.set_defaults(func=device.db_delete)

# TODO: add support for device.db_del_ctrl_of and db_del_resp_of.
# The problem is the modem can't handle those commands. Best way
# to fix this is to re-code Modem.db_delete to be smart and delete
# all the entries and then re-add the ones that weren't the input
# command. That way from the outside the modem and devices have
# the same API.

return p.parse_args(args)


Expand Down
12 changes: 0 additions & 12 deletions insteon_mqtt/cmd_line/modem.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@
from . import util


#===========================================================================
def set_btn(args, config):
topic = "%s/modem" % (args.topic)
payload = {
"cmd" : "set_btn",
"time_out" : args.timeout,
}

reply = util.send(config, topic, payload, args.quiet)
return reply["result"]


#===========================================================================
def refresh_all(args, config):
topic = "%s/modem" % (args.topic)
Expand Down
Loading

0 comments on commit 58cd68c

Please sign in to comment.