swarmit 0.3.0__py3-none-any.whl → 0.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: swarmit
3
+ Version: 0.4.4
4
+ Summary: Run Your Own Robot Swarm Testbed.
5
+ Project-URL: Homepage, https://github.com/DotBots/swarmit
6
+ Project-URL: Bug Tracker, https://github.com/DotBots/swarmit/issues
7
+ Author-email: Alexandre Abadie <alexandre.abadie@inria.fr>
8
+ License: BSD
9
+ License-File: AUTHORS
10
+ License-File: LICENSE
11
+ Classifier: License :: OSI Approved :: BSD License
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Requires-Python: >=3.7
17
+ Requires-Dist: click==8.1.7
18
+ Requires-Dist: cryptography==43.0.1
19
+ Requires-Dist: marilib-pkg>=0.6.0
20
+ Requires-Dist: pydotbot>=0.24.1
21
+ Requires-Dist: rich==14.0.0
22
+ Requires-Dist: structlog==24.4.0
23
+ Requires-Dist: tqdm==4.66.5
24
+ Description-Content-Type: text/markdown
25
+
26
+ # SwarmIT
27
+
28
+ SwarmIT provides a embedded C port for nRF53 as well as Python based services to
29
+ easily build and deploy a robotic swarm infrastructure testbed.
30
+ ARM TrustZone is used to create a sandboxed user environment on each device
31
+ under test, without requiring a control co-processor attached to it.
32
+
33
+ https://github.com/user-attachments/assets/eff63b07-216a-41fb-9062-2e0e56f03c20
34
+
35
+ ## Features
36
+
37
+ - Experiment management: start, stop, monitor and status check
38
+ - Deploy a custom firmware on all or on a subset of robots of a swarm testbed
39
+ - Resilient robot state: even when crashed by buggy user code, the robot can be reprogrammed remotely and wirelessly
40
+
41
+ ## Usage
42
+
43
+ ### Get the code
44
+
45
+ Swarmit depends on the [DotBot-firmware](https://github.com/DotBots/DotBot-firmware)
46
+ and [Mari](https://github.com/DotBots/mari) repositories. They are included
47
+ in the codebase as [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules).
48
+
49
+ Use the following command to clone the Swarmit codebase locally:
50
+
51
+ ```
52
+ git clone --recurse-submodules https://github.com/DotBots/swarmit.git
53
+ ```
54
+
55
+ ### Embedded C code
56
+
57
+ SwarmIT embedded C code can be built using
58
+ [Segger Embedded Studio (SES)](https://www.segger.com/products/development-tools/embedded-studio/).
59
+ Use Tools > Package manager to install the CMSIS 5 CMSIS-CORE, CMSIS-DSP and nRF packages.
60
+
61
+ To provision a device, follow the following steps:
62
+ 1. open [netcore.emProject](swarmit-netcore.emProject)
63
+ and [bootloader.emProject](swarmit-bootloader-dotbot-v3.emProject)
64
+ (or [bootloader.emProject](swarmit-bootloader-dotbot-v2.emProject) depending on
65
+ your robot version) in SES
66
+ 2. build and load the netcore application on the nRF53 network core,
67
+ 3. build and load the bootloader application on the nRF53 application core.
68
+
69
+ The device is now ready.
70
+
71
+ ### Gateway
72
+
73
+ The communication between the computer and the swarm devices is performed via a
74
+ gateway board connected via USB to the computer.
75
+
76
+ This gateway uses the [mari](https://github.com/dotbots/mari) network stack and
77
+ the it must run the Mari gateway firmware, either Edge or Cloud version.
78
+
79
+ The documentation to setup a Mari gateway is located
80
+ [here](https://github.com/DotBots/mari/wiki/Getting-started#running-mari-network-on-your-computer).
81
+
82
+ ### Python CLI script
83
+
84
+ The Python CLI script provides commands for flashing, starting and stopping user
85
+ code on the device, as well as monitoring and checking the status of devices
86
+ in the swarm.
87
+
88
+ The Python CLI script connects via a virtual COM port to the gateway connected to
89
+ the computer.
90
+
91
+ The Python CLI script is available on PyPI. Install it using:
92
+
93
+ ```
94
+ pip install swarmit
95
+ ```
96
+
97
+ Print usage using `swarmit --help`:
98
+
99
+ ```
100
+ Usage: swarmit [OPTIONS] COMMAND [ARGS]...
101
+
102
+ Options:
103
+ -p, --port TEXT Serial port to use to send the bitstream to
104
+ the gateway. Default: /dev/ttyACM0.
105
+ -b, --baudrate INTEGER Serial port baudrate. Default: 1000000.
106
+ -H, --mqtt-host TEXT MQTT host. Default: localhost.
107
+ -P, --mqtt-port INTEGER MQTT port. Default: 1883.
108
+ -T, --mqtt-use_tls Use TLS with MQTT.
109
+ -n, --network-id INTEGER Marilib network ID to use. Default: 1
110
+ -a, --adapter [edge|cloud]
111
+ Choose the adapter to communicate with the
112
+ gateway. [default: edge]
113
+ -d, --devices TEXT Subset list of devices to interact with,
114
+ separated with ,
115
+ -v, --verbose Enable verbose mode.
116
+ -V, --version Show the version and exit.
117
+ -h, --help Show this message and exit.
118
+
119
+ Commands:
120
+ flash Flash a firmware to the robots.
121
+ message Send a custom text message to the robots.
122
+ monitor Monitor running applications.
123
+ reset Reset robots locations.
124
+ start Start the user application.
125
+ status Print current status of the robots.
126
+ stop Stop the user application.
127
+ ```
@@ -0,0 +1,12 @@
1
+ dotbot-firmware/doc/sphinx/conf.py,sha256=uQJVglqE9HoqMXJf5nbmCVCLSSGbqfiSr5ZQbE7HsLk,6211
2
+ testbed/cli/main.py,sha256=2jix2xPzP4XLcnpk804gKGOJWnXAho73lbZXqJGk_6g,9399
3
+ testbed/swarmit/__init__.py,sha256=6G_giX6Ucuweo7w5OiftoXmbNLoqiU_soXJoU8aiLmY,22
4
+ testbed/swarmit/adapter.py,sha256=Qmx7ULkx0OfyU7PJh-VBQibEOhvnDfnB3_DmntQ2tfU,4616
5
+ testbed/swarmit/controller.py,sha256=7XTX0Px8hnzE-ebUsMLFuPs3uKOrdBSle2mkOuW_gVQ,22354
6
+ testbed/swarmit/protocol.py,sha256=tkPMIGo_G1Fvo3zM6_zk4a3i3gqLtEzDFf97VRHa4B4,6344
7
+ swarmit-0.4.4.dist-info/METADATA,sha256=tsOWOAwVvJNzJxJleyTLcTcaaqFjuYgmSVaEr2A10xE,4837
8
+ swarmit-0.4.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ swarmit-0.4.4.dist-info/entry_points.txt,sha256=R6BGQe6I1FcI5B2jo7Dr-Gh6-Rjn1Ykx3uAGuV5rVTo,50
10
+ swarmit-0.4.4.dist-info/licenses/AUTHORS,sha256=o2cH3J5JkbZssK_1zYj0m8PHiGiILV0lySR6_hoWKK0,45
11
+ swarmit-0.4.4.dist-info/licenses/LICENSE,sha256=j97C1uBc5chpQWi4bv_2SrqExuvKaJK2Ch6L2LFkoc4,1492
12
+ swarmit-0.4.4.dist-info/RECORD,,
testbed/cli/main.py CHANGED
@@ -14,49 +14,100 @@ from rich.pretty import pprint
14
14
  from testbed.swarmit import __version__
15
15
  from testbed.swarmit.controller import (
16
16
  CHUNK_SIZE,
17
+ OTA_ACK_TIMEOUT_DEFAULT,
18
+ OTA_MAX_RETRIES_DEFAULT,
17
19
  Controller,
18
20
  ControllerSettings,
19
21
  ResetLocation,
20
- print_start_status,
21
- print_status,
22
- print_stop_status,
23
22
  print_transfer_status,
24
23
  )
25
24
 
26
25
  SERIAL_PORT_DEFAULT = get_default_port()
27
26
  BAUDRATE_DEFAULT = 1000000
27
+ MQTT_HOST_DEFAULT = "localhost"
28
+ MQTT_PORT_DEFAULT = 1883
29
+ # Default network ID for SwarmIT tests is 0x12**
30
+ # See https://crystalfree.atlassian.net/wiki/spaces/Mari/pages/3324903426/Registry+of+Mari+Network+IDs
31
+ SWARMIT_NETWORK_ID_DEFAULT = "1200"
28
32
 
29
33
 
30
34
  @click.group(context_settings=dict(help_option_names=["-h", "--help"]))
31
- @click.version_option(version=__version__)
32
35
  @click.option(
33
36
  "-p",
34
37
  "--port",
38
+ type=str,
35
39
  default=SERIAL_PORT_DEFAULT,
36
40
  help=f"Serial port to use to send the bitstream to the gateway. Default: {SERIAL_PORT_DEFAULT}.",
37
41
  )
38
42
  @click.option(
39
43
  "-b",
40
44
  "--baudrate",
45
+ type=int,
41
46
  default=BAUDRATE_DEFAULT,
42
47
  help=f"Serial port baudrate. Default: {BAUDRATE_DEFAULT}.",
43
48
  )
44
49
  @click.option(
45
- "-e",
46
- "--edge",
50
+ "-H",
51
+ "--mqtt-host",
52
+ type=str,
53
+ default=MQTT_HOST_DEFAULT,
54
+ help=f"MQTT host. Default: {MQTT_HOST_DEFAULT}.",
55
+ )
56
+ @click.option(
57
+ "-P",
58
+ "--mqtt-port",
59
+ type=int,
60
+ default=MQTT_PORT_DEFAULT,
61
+ help=f"MQTT port. Default: {MQTT_PORT_DEFAULT}.",
62
+ )
63
+ @click.option(
64
+ "-T",
65
+ "--mqtt-use_tls",
47
66
  is_flag=True,
48
- default=False,
49
- help="Use MQTT adapter to communicate with an edge gateway.",
67
+ help="Use TLS with MQTT.",
68
+ )
69
+ @click.option(
70
+ "-n",
71
+ "--network-id",
72
+ type=str,
73
+ default=SWARMIT_NETWORK_ID_DEFAULT,
74
+ help=f"Marilib network ID to use. Default: 0x{SWARMIT_NETWORK_ID_DEFAULT}",
75
+ )
76
+ @click.option(
77
+ "-a",
78
+ "--adapter",
79
+ type=click.Choice(["edge", "cloud"], case_sensitive=True),
80
+ default="edge",
81
+ show_default=True,
82
+ help="Choose the adapter to communicate with the gateway.",
50
83
  )
51
84
  @click.option(
52
85
  "-d",
53
86
  "--devices",
54
87
  type=str,
55
88
  default="",
56
- help="Subset list of devices to interact with, separated with ,",
89
+ help="Subset list of device addresses to interact with, separated with ,",
57
90
  )
91
+ @click.option(
92
+ "-v",
93
+ "--verbose",
94
+ is_flag=True,
95
+ help="Enable verbose mode.",
96
+ )
97
+ @click.version_option(__version__, "-V", "--version", prog_name="swarmit")
58
98
  @click.pass_context
59
- def main(ctx, port, baudrate, edge, devices):
99
+ def main(
100
+ ctx,
101
+ port,
102
+ baudrate,
103
+ mqtt_host,
104
+ mqtt_port,
105
+ mqtt_use_tls,
106
+ network_id,
107
+ adapter,
108
+ devices,
109
+ verbose,
110
+ ):
60
111
  if ctx.invoked_subcommand != "monitor":
61
112
  # Disable logging if not monitoring
62
113
  structlog.configure(
@@ -65,32 +116,25 @@ def main(ctx, port, baudrate, edge, devices):
65
116
  ),
66
117
  )
67
118
  ctx.ensure_object(dict)
68
- ctx.obj["port"] = port
69
- ctx.obj["baudrate"] = baudrate
70
- ctx.obj["edge"] = edge
71
- ctx.obj["devices"] = [e for e in devices.split(",") if e]
119
+ ctx.obj["settings"] = ControllerSettings(
120
+ serial_port=port,
121
+ serial_baudrate=baudrate,
122
+ mqtt_host=mqtt_host,
123
+ mqtt_port=mqtt_port,
124
+ mqtt_use_tls=mqtt_use_tls,
125
+ network_id=int(network_id, 16),
126
+ adapter=adapter,
127
+ devices=[d for d in devices.split(",") if d],
128
+ verbose=verbose,
129
+ )
72
130
 
73
131
 
74
132
  @main.command()
75
- @click.option(
76
- "-v",
77
- "--verbose",
78
- is_flag=True,
79
- help="Print start result.",
80
- )
81
133
  @click.pass_context
82
- def start(ctx, verbose):
134
+ def start(ctx):
83
135
  """Start the user application."""
84
136
  try:
85
- settings = ControllerSettings(
86
- serial_port=ctx.obj["port"],
87
- serial_baudrate=ctx.obj["baudrate"],
88
- mqtt_host="argus.paris.inria.fr",
89
- mqtt_port=8883,
90
- edge=ctx.obj["edge"],
91
- devices=list(ctx.obj["devices"]),
92
- )
93
- controller = Controller(settings)
137
+ controller = Controller(ctx.obj["settings"])
94
138
  except (
95
139
  SerialInterfaceException,
96
140
  serial.serialutil.SerialException,
@@ -99,43 +143,18 @@ def start(ctx, verbose):
99
143
  console.print(f"[bold red]Error:[/] {exc}")
100
144
  return
101
145
  if controller.ready_devices:
102
- started = controller.start()
103
- print_start_status(
104
- sorted(started),
105
- sorted(set(controller.ready_devices).difference(set(started))),
106
- )
107
- if verbose:
108
- print("Started devices:")
109
- pprint(started)
110
- print("Not started devices:")
111
- pprint(
112
- sorted(set(controller.ready_devices).difference(set(started)))
113
- )
146
+ controller.start()
114
147
  else:
115
148
  print("No device to start")
116
149
  controller.terminate()
117
150
 
118
151
 
119
152
  @main.command()
120
- @click.option(
121
- "-v",
122
- "--verbose",
123
- is_flag=True,
124
- help="Print start result.",
125
- )
126
153
  @click.pass_context
127
- def stop(ctx, verbose):
154
+ def stop(ctx):
128
155
  """Stop the user application."""
129
156
  try:
130
- settings = ControllerSettings(
131
- serial_port=ctx.obj["port"],
132
- serial_baudrate=ctx.obj["baudrate"],
133
- mqtt_host="argus.paris.inria.fr",
134
- mqtt_port=8883,
135
- edge=ctx.obj["edge"],
136
- devices=list(ctx.obj["devices"]),
137
- )
138
- controller = Controller(settings)
157
+ controller = Controller(ctx.obj["settings"])
139
158
  except (
140
159
  SerialInterfaceException,
141
160
  serial.serialutil.SerialException,
@@ -144,29 +163,9 @@ def stop(ctx, verbose):
144
163
  console.print(f"[bold red]Error:[/] {exc}")
145
164
  return
146
165
  if controller.running_devices or controller.resetting_devices:
147
- stopped = controller.stop()
148
- print_stop_status(
149
- sorted(stopped),
150
- sorted(
151
- set(
152
- controller.running_devices + controller.resetting_devices
153
- ).difference(set(stopped))
154
- ),
155
- )
156
- if verbose:
157
- print("Started devices:")
158
- pprint(stopped)
159
- print("Not started devices:")
160
- pprint(
161
- sorted(
162
- set(
163
- controller.running_devices
164
- + controller.resetting_devices
165
- ).difference(set(stopped))
166
- )
167
- )
166
+ controller.stop()
168
167
  else:
169
- print("No device to stop")
168
+ print("[bold]No device to stop[/]")
170
169
  controller.terminate()
171
170
 
172
171
 
@@ -179,39 +178,32 @@ def stop(ctx, verbose):
179
178
  def reset(ctx, locations):
180
179
  """Reset robots locations.
181
180
 
182
- Locations are provided as '<device_id>:<x>,<y>-<device_id>:<x>,<y>|...'
181
+ Locations are provided as '<device_addr>:<x>,<y>-<device_addr>:<x>,<y>|...'
183
182
  """
184
- devices = ctx.obj["devices"]
183
+ try:
184
+ controller = Controller(ctx.obj["settings"])
185
+ except (
186
+ SerialInterfaceException,
187
+ serial.serialutil.SerialException,
188
+ ) as exc:
189
+ console = Console()
190
+ console.print(f"[bold red]Error:[/] {exc}")
191
+ return
192
+
193
+ devices = controller.settings.devices
185
194
  if not devices:
186
195
  print("No devices selected.")
187
196
  return
188
197
  locations = {
189
- location.split(':')[0]: ResetLocation(
190
- pos_x=int(float(location.split(':')[1].split(',')[0]) * 1e6),
191
- pos_y=int(float(location.split(':')[1].split(',')[1]) * 1e6),
198
+ int(location.split(":")[0], 16): ResetLocation(
199
+ pos_x=int(float(location.split(":")[1].split(",")[0]) * 1e6),
200
+ pos_y=int(float(location.split(":")[1].split(",")[1]) * 1e6),
192
201
  )
193
202
  for location in locations.split("-")
194
203
  }
195
204
  if sorted(devices) and sorted(locations.keys()) != sorted(devices):
196
205
  print("Selected devices and reset locations do not match.")
197
206
  return
198
- try:
199
- settings = ControllerSettings(
200
- serial_port=ctx.obj["port"],
201
- serial_baudrate=ctx.obj["baudrate"],
202
- mqtt_host="argus.paris.inria.fr",
203
- mqtt_port=8883,
204
- edge=ctx.obj["edge"],
205
- devices=list(ctx.obj["devices"]),
206
- )
207
- controller = Controller(settings)
208
- except (
209
- SerialInterfaceException,
210
- serial.serialutil.SerialException,
211
- ) as exc:
212
- console = Console()
213
- console.print(f"[bold red]Error:[/] {exc}")
214
- return
215
207
  if not controller.ready_devices:
216
208
  print("No device to reset.")
217
209
  return
@@ -233,32 +225,35 @@ def reset(ctx, locations):
233
225
  help="Start the firmware once flashed.",
234
226
  )
235
227
  @click.option(
236
- "-v",
237
- "--verbose",
238
- is_flag=True,
239
- help="Print transfer data.",
228
+ "-t",
229
+ "--ota-timeout",
230
+ type=float,
231
+ default=OTA_ACK_TIMEOUT_DEFAULT,
232
+ show_default=True,
233
+ help="Timeout in seconds for each OTA ACK message.",
234
+ )
235
+ @click.option(
236
+ "-r",
237
+ "--ota-max-retries",
238
+ type=int,
239
+ default=OTA_MAX_RETRIES_DEFAULT,
240
+ show_default=True,
241
+ help="Number of retries for each OTA message (start or chunk) transfer.",
240
242
  )
241
243
  @click.argument("firmware", type=click.File(mode="rb"), required=False)
242
244
  @click.pass_context
243
- def flash(ctx, yes, start, verbose, firmware):
245
+ def flash(ctx, yes, start, ota_timeout, ota_max_retries, firmware):
244
246
  """Flash a firmware to the robots."""
245
247
  console = Console()
246
248
  if firmware is None:
247
249
  console.print("[bold red]Error:[/] Missing firmware file. Exiting.")
248
250
  ctx.exit()
249
-
251
+ ctx.obj["settings"].ota_timeout = ota_timeout
252
+ ctx.obj["settings"].ota_max_retries = ota_max_retries
250
253
  fw = bytearray(firmware.read())
251
- settings = ControllerSettings(
252
- serial_port=ctx.obj["port"],
253
- serial_baudrate=ctx.obj["baudrate"],
254
- mqtt_host="argus.paris.inria.fr",
255
- mqtt_port=8883,
256
- edge=ctx.obj["edge"],
257
- devices=ctx.obj["devices"],
258
- )
259
- controller = Controller(settings)
254
+ controller = Controller(ctx.obj["settings"])
260
255
  if not controller.ready_devices:
261
- console.print("[bold red]Error:[/] No ready devices found. Exiting.")
256
+ console.print("[bold red]Error:[/] No ready device found. Exiting.")
262
257
  controller.terminate()
263
258
  return
264
259
  print(
@@ -268,41 +263,44 @@ def flash(ctx, yes, start, verbose, firmware):
268
263
  if yes is False:
269
264
  click.confirm("Do you want to continue?", default=True, abort=True)
270
265
 
271
- devices = controller.settings.devices
272
266
  start_data = controller.start_ota(fw)
273
- if (devices and sorted(start_data.ids) != sorted(devices)) or (
274
- not devices
275
- and sorted(start_data.ids) != sorted(controller.ready_devices)
276
- ):
267
+ if controller.settings.verbose:
268
+ print("\n[b]Start OTA response:[/]")
269
+ pprint(start_data, indent_guides=False, expand_all=True)
270
+ if start_data["missed"]:
277
271
  console = Console()
278
272
  console.print(
279
- "[bold red]Error:[/] some acknowledgments are missing "
280
- f'({", ".join(sorted(set(controller.ready_devices).difference(set(start_data.ids))))}). '
273
+ f"[bold red]Error:[/] {len(start_data["missed"])} acknowledgments "
274
+ f"are missing ({', '.join(sorted(set(start_data["missed"])))}). "
281
275
  "Aborting."
282
276
  )
277
+ controller.stop()
278
+ controller.terminate()
283
279
  raise click.Abort()
284
280
  print()
285
281
  print(f"Image size: [bold cyan]{len(fw)}B[/]")
286
- print(f"Image hash: [bold cyan]{start_data.fw_hash.hex().upper()}[/]")
287
- print(f"Radio chunks ([bold]{CHUNK_SIZE}B[/bold]): {start_data.chunks}")
282
+ print(
283
+ f"Image hash: [bold cyan]{start_data["ota"].fw_hash.hex().upper()}[/]"
284
+ )
285
+ print(
286
+ f"Radio chunks ([bold]{CHUNK_SIZE}B[/bold]): {start_data["ota"].chunks}"
287
+ )
288
288
  start_time = time.time()
289
- data = controller.transfer(fw)
289
+ data = controller.transfer(fw, start_data["acked"])
290
290
  print(f"Elapsed: [bold cyan]{time.time() - start_time:.3f}s[/bold cyan]")
291
- print_transfer_status(data, start_data)
292
- if verbose:
293
- pprint(data)
294
- if not all([value.hashes_match for value in data.values()]):
291
+ print_transfer_status(data, start_data["ota"])
292
+ if controller.settings.verbose:
293
+ print("\n[b]Transfer data:[/]")
294
+ pprint(data, indent_guides=False, expand_all=True)
295
+ if all([device.success for device in data.values()]) is False:
295
296
  controller.terminate()
296
297
  console = Console()
297
- console.print("[bold red]Error:[/] Hashes do not match.")
298
+ console.print("[bold red]Error:[/] Transfer failed.")
298
299
  raise click.Abort()
299
300
 
300
301
  if start is True:
301
- started = controller.start()
302
- print_start_status(
303
- sorted(started),
304
- sorted(set(start_data.ids).difference(set(started))),
305
- )
302
+ time.sleep(1)
303
+ controller.start()
306
304
  controller.terminate()
307
305
 
308
306
 
@@ -311,15 +309,7 @@ def flash(ctx, yes, start, verbose, firmware):
311
309
  def monitor(ctx):
312
310
  """Monitor running applications."""
313
311
  try:
314
- settings = ControllerSettings(
315
- serial_port=ctx.obj["port"],
316
- serial_baudrate=ctx.obj["baudrate"],
317
- mqtt_host="argus.paris.inria.fr",
318
- mqtt_port=8883,
319
- edge=ctx.obj["edge"],
320
- devices=ctx.obj["devices"],
321
- )
322
- controller = Controller(settings)
312
+ controller = Controller(ctx.obj["settings"])
323
313
  except (
324
314
  SerialInterfaceException,
325
315
  serial.serialutil.SerialException,
@@ -339,20 +329,8 @@ def monitor(ctx):
339
329
  @click.pass_context
340
330
  def status(ctx):
341
331
  """Print current status of the robots."""
342
- settings = ControllerSettings(
343
- serial_port=ctx.obj["port"],
344
- serial_baudrate=ctx.obj["baudrate"],
345
- mqtt_host="argus.paris.inria.fr",
346
- mqtt_port=8883,
347
- edge=ctx.obj["edge"],
348
- devices=ctx.obj["devices"],
349
- )
350
- controller = Controller(settings)
351
- data = controller.status()
352
- if not data:
353
- click.echo("No devices found.")
354
- else:
355
- print_status(data)
332
+ controller = Controller(ctx.obj["settings"])
333
+ controller.status()
356
334
  controller.terminate()
357
335
 
358
336
 
@@ -361,15 +339,7 @@ def status(ctx):
361
339
  @click.pass_context
362
340
  def message(ctx, message):
363
341
  """Send a custom text message to the robots."""
364
- settings = ControllerSettings(
365
- serial_port=ctx.obj["port"],
366
- serial_baudrate=ctx.obj["baudrate"],
367
- mqtt_host="argus.paris.inria.fr",
368
- mqtt_port=8883,
369
- edge=ctx.obj["edge"],
370
- devices=ctx.obj["devices"],
371
- )
372
- controller = Controller(settings)
342
+ controller = Controller(ctx.obj["settings"])
373
343
  controller.send_message(message)
374
344
  controller.terminate()
375
345
 
@@ -1 +1 @@
1
- __version__ = "0.3.0"
1
+ __version__ = "0.4.4"