Skip to content

Commit 6cdb78e

Browse files
authored
Tool: add a tool to configure sink (#174)
This tool is available with wm-node-conf It can locally configure a node through DBUS (with a sink service attached) but can also be used to list sinks config
1 parent f725540 commit 6cdb78e

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed

python_transport/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def get_requirements(*args):
8888
"console_scripts": [
8989
"wm-gw=wirepas_gateway.transport_service:main",
9090
"wm-dbus-print=wirepas_gateway.dbus_print_client:main",
91+
"wm-node-conf=wirepas_gateway.configure_node:main",
9192
]
9293
},
9394
)
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# Copyright 2020 Wirepas Ltd licensed under Apache License, Version 2.0
2+
#
3+
# See file LICENSE for full license details.
4+
#
5+
import binascii
6+
import argparse
7+
8+
from enum import Enum
9+
10+
from wirepas_gateway.dbus.dbus_client import BusClient
11+
12+
13+
class NodeRole:
14+
"""Utility class to package a node role
15+
16+
Helper to manipulate and convert node role based on a Base role
17+
and a list of flags
18+
19+
"""
20+
21+
class BaseRole(Enum):
22+
SINK = 0x1
23+
ROUTEUR = 0x2
24+
NON_ROUTEUR = 0x3
25+
26+
class RoleFlags(Enum):
27+
CSMA_CA = 0x1
28+
AUTOROLE = 0x8
29+
30+
def __init__(self, base, flags):
31+
"""Base role constructor
32+
33+
Args:
34+
base: a BaseRole
35+
flags: list of RoleFlags
36+
"""
37+
self.base = base
38+
self.flags = flags
39+
40+
@classmethod
41+
def from_string(cls, node_role_str):
42+
"""Create a NodeRole from a string
43+
44+
Args:
45+
node_role_str: string containing the node role
46+
47+
Returns: A NodeRole object
48+
"""
49+
str_lower = node_role_str.lower()
50+
if "sink" in str_lower:
51+
base = cls.BaseRole.SINK
52+
elif "non-router" in str_lower:
53+
base = cls.BaseRole.NON_ROUTEUR
54+
elif "router" in str_lower:
55+
base = cls.BaseRole.ROUTEUR
56+
else:
57+
raise ValueError("Cannot determine base role from %s" % node_role_str)
58+
59+
flags = []
60+
if "csma-ca" in str_lower:
61+
flags.append(cls.RoleFlags.CSMA_CA)
62+
63+
if "autorole" in str_lower:
64+
flags.append(cls.RoleFlags.AUTOROLE)
65+
66+
return cls(base, flags)
67+
68+
@classmethod
69+
def from_dualmcu_value(cls, val):
70+
"""Create a NodeRole from a dualmcu value
71+
72+
Args:
73+
val: node role as a 1 byte value from dual mcu api
74+
75+
Returns: A NodeRole object
76+
"""
77+
base_int = val & 0x0F
78+
flags_int = val & 0xF0
79+
80+
# Define base role
81+
if base_int == 1:
82+
base = cls.BaseRole.SINK
83+
elif base_int == 2:
84+
base = cls.BaseRole.ROUTEUR
85+
elif base_int == 3:
86+
base = cls.BaseRole.NON_ROUTEUR
87+
88+
# Define flags
89+
flags = []
90+
if flags_int & 0x10 != 0:
91+
flags.append(cls.RoleFlags.CSMA_CA)
92+
93+
if flags_int & 0x80 != 0:
94+
flags.append(cls.RoleFlags.AUTOROLE)
95+
96+
return cls(base, flags)
97+
98+
def to_dualmcu_value(self):
99+
"""Convert node role to dual mcu api value
100+
101+
Returns: dualmcu value for this role
102+
"""
103+
val = 0
104+
if self.base == self.BaseRole.SINK:
105+
val += 1
106+
elif self.base == self.BaseRole.ROUTEUR:
107+
val += 2
108+
elif self.base == self.BaseRole.NON_ROUTEUR:
109+
val += 3
110+
111+
if self.RoleFlags.CSMA_CA in self.flags:
112+
val += 0x10
113+
114+
if self.RoleFlags.AUTOROLE in self.flags:
115+
val += 0x80
116+
117+
return val
118+
119+
def __str__(self):
120+
flag_str = ""
121+
for flag in self.flags:
122+
flag_str += str(flag.name)
123+
flag_str += " "
124+
125+
return "%s, %s" % (self.base.name, flag_str)
126+
127+
128+
class SinkConfigurator(BusClient):
129+
"""Simple class to configure a node
130+
131+
Use the Dbus api to set or read nodes configuration
132+
"""
133+
134+
def configure(
135+
self,
136+
sink_name,
137+
node_address=None,
138+
node_role=None,
139+
network_address=None,
140+
network_channel=None,
141+
start=None,
142+
authentication_key=None,
143+
cipher_key=None,
144+
):
145+
sink = self.sink_manager.get_sink(sink_name)
146+
if sink is None:
147+
print("Cannot retrieve sink object with name %s" % sink_name)
148+
return
149+
150+
# Do the actual configuration
151+
config = {}
152+
if node_address is not None:
153+
config["node_address"] = node_address
154+
if node_role is not None:
155+
config["node_role"] = node_role.to_dualmcu_value()
156+
if network_address is not None:
157+
config["network_address"] = network_address
158+
if network_channel is not None:
159+
config["network_channel"] = network_channel
160+
if start is not None:
161+
config["started"] = start
162+
if cipher_key is not None:
163+
config["cipher_key"] = cipher_key
164+
if authentication_key is not None:
165+
config["authentication_key"] = authentication_key
166+
167+
ret = sink.write_config(config)
168+
print("Configuration done with result = {}".format(ret))
169+
170+
def list_sinks(self):
171+
sinks = self.sink_manager.get_sinks()
172+
173+
print("List of sinks:")
174+
for sink in sinks:
175+
print("============== [%s] ===============" % sink.sink_id)
176+
config = sink.read_config()
177+
for key in config.keys():
178+
if key == "node_role":
179+
print("[%s]: %s" % (key, NodeRole.from_dualmcu_value(config[key])))
180+
elif key == "app_config_data":
181+
print("[%s]: %s" % (key, binascii.hexlify(config[key])))
182+
else:
183+
print("[%s]: %s" % (key, config[key]))
184+
print("===================================")
185+
186+
187+
def key_type(param_str):
188+
"""Ensure key type conversion
189+
190+
Args:
191+
param_str: the key value as a string given by user
192+
193+
Returns: The key as a bytearray object
194+
"""
195+
key_str = param_str.replace(",", " ")
196+
key = bytearray.fromhex(key_str)
197+
198+
if len(key) != 16:
199+
raise argparse.ArgumentTypeError("Key is not 128 bits long")
200+
201+
return key
202+
203+
204+
def node_role_type(param_str):
205+
"""Ensure node role conversion
206+
207+
Args:
208+
param_str: the parameter value as a string given by user
209+
210+
Returns: The role as a NodeRole object
211+
"""
212+
try:
213+
return NodeRole.from_string(param_str)
214+
except ValueError as e:
215+
raise argparse.ArgumentTypeError(str(e))
216+
217+
218+
def bool_type(param_str):
219+
"""Ensures string to bool conversion
220+
221+
Args:
222+
param_str: the parameter value as a string given by user
223+
224+
Returns: The value as a boolean
225+
"""
226+
if param_str.lower() in ("yes", "true", "t", "y", "1"):
227+
return True
228+
elif param_str.lower() in ("no", "false", "f", "n", "0", ""):
229+
return False
230+
else:
231+
raise argparse.ArgumentTypeError("Boolean value expected.")
232+
233+
234+
def int_type(param_str):
235+
""" Ensures string to int conversion to accept 0x format
236+
237+
Args:
238+
param_str: the parameter value as a string given by user
239+
240+
Returns: The value as a boolean
241+
"""
242+
try:
243+
value = int(param_str, 0)
244+
except ValueError:
245+
raise argparse.ArgumentTypeError("Integer value expected.")
246+
return value
247+
248+
249+
def main():
250+
"""Main service to configure a node locally on the gateway
251+
252+
This tool allows to interact locally on the gateway to configure a node
253+
with dbus api
254+
"""
255+
256+
parser = argparse.ArgumentParser()
257+
parser.add_argument("cmd", choices=["list", "set"])
258+
259+
parser.add_argument(
260+
"-s",
261+
"--sink_name",
262+
type=str,
263+
help="Sink name as configured in sinkService. Ex: -s sink0",
264+
)
265+
266+
parser.add_argument(
267+
"-n",
268+
"--node_address",
269+
type=int_type,
270+
help="Node address as an int. Ex: -n 0xABC or -n 123",
271+
)
272+
273+
parser.add_argument(
274+
"-r",
275+
"--node_role",
276+
type=node_role_type,
277+
help="Node role as a string being any combination of a base role "
278+
"sink/router/non-router and list of flags from [csma-ca, autorole]."
279+
'Ex: -r "sink csma-ca" or -r "router csma-ca autorole"',
280+
)
281+
282+
parser.add_argument(
283+
"-N",
284+
"--network_address",
285+
type=int_type,
286+
help="Network address as an int. Ex: -N 0xA1B2C3 or -N 123456",
287+
)
288+
289+
parser.add_argument(
290+
"-c",
291+
"--network_channel",
292+
type=int_type,
293+
help="Network channel as an int. Ex: -c 5",
294+
)
295+
296+
parser.add_argument(
297+
"-ak",
298+
"--authentication_key",
299+
type=key_type,
300+
help="Network wide 128 bytes authentication key. "
301+
"Ex: -ak 1122334455667788AABBCCDDEEFF11 "
302+
"or -ak 11,22,33,44,55,66,77,88,AA,BB,CC,DD,EE,FF,11",
303+
)
304+
305+
parser.add_argument(
306+
"-ck",
307+
"--cipher_key",
308+
type=key_type,
309+
help="Network wide cipher key. "
310+
"Ex: -ak 1122334455667788AABBCCDDEEFF11 "
311+
"or -ak 11,22,33,44,55,66,77,88,AA,BB,CC,DD,EE,FF,11",
312+
)
313+
314+
parser.add_argument(
315+
"-S", "--start", type=bool_type, help="Start the sink after configuration"
316+
)
317+
318+
args = parser.parse_args()
319+
320+
sink_configurator = SinkConfigurator()
321+
322+
if args.cmd == "list":
323+
sink_configurator.list_sinks()
324+
325+
if args.cmd == "set":
326+
sink_configurator.configure(
327+
node_address=args.node_address,
328+
node_role=args.node_role,
329+
network_address=args.network_address,
330+
network_channel=args.network_channel,
331+
sink_name=args.sink_name,
332+
start=args.start,
333+
authentication_key=args.authentication_key,
334+
cipher_key=args.cipher_key,
335+
)
336+
337+
338+
if __name__ == "__main__":
339+
main()

0 commit comments

Comments
 (0)