ephys-link 1.2.5__tar.gz → 1.2.8__tar.gz

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.
Files changed (32) hide show
  1. {ephys_link-1.2.5 → ephys_link-1.2.8}/PKG-INFO +8 -1
  2. {ephys_link-1.2.5 → ephys_link-1.2.8}/README.md +6 -0
  3. {ephys_link-1.2.5 → ephys_link-1.2.8}/pyproject.toml +2 -0
  4. ephys_link-1.2.8/src/ephys_link/__about__.py +1 -0
  5. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/__main__.py +13 -11
  6. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/common.py +12 -0
  7. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/server.py +77 -38
  8. ephys_link-1.2.5/src/ephys_link/__about__.py +0 -1
  9. {ephys_link-1.2.5 → ephys_link-1.2.8}/.gitignore +0 -0
  10. {ephys_link-1.2.5 → ephys_link-1.2.8}/LICENSE +0 -0
  11. {ephys_link-1.2.5 → ephys_link-1.2.8}/assets/icon.ico +0 -0
  12. {ephys_link-1.2.5 → ephys_link-1.2.8}/ephys_link.spec +0 -0
  13. {ephys_link-1.2.5 → ephys_link-1.2.8}/scripts/__init__.py +0 -0
  14. {ephys_link-1.2.5 → ephys_link-1.2.8}/scripts/move_tester.py +0 -0
  15. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/__init__.py +0 -0
  16. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/emergency_stop.py +0 -0
  17. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/gui.py +0 -0
  18. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platform_handler.py +0 -0
  19. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platform_manipulator.py +0 -0
  20. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/__init__.py +0 -0
  21. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/new_scale_handler.py +0 -0
  22. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/new_scale_manipulator.py +0 -0
  23. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/new_scale_pathfinder_handler.py +0 -0
  24. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/sensapex_handler.py +0 -0
  25. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/sensapex_manipulator.py +0 -0
  26. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/ump3_handler.py +0 -0
  27. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/platforms/ump3_manipulator.py +0 -0
  28. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/resources/CP210xManufacturing.dll +0 -0
  29. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/resources/NstMotorCtrl.dll +0 -0
  30. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/resources/SiUSBXp.dll +0 -0
  31. {ephys_link-1.2.5 → ephys_link-1.2.8}/src/ephys_link/resources/libum.dll +0 -0
  32. {ephys_link-1.2.5 → ephys_link-1.2.8}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ephys-link
3
- Version: 1.2.5
3
+ Version: 1.2.8
4
4
  Summary: A Python Socket.IO server that allows any Socket.IO-compliant application to communicate with manipulators used in electrophysiology experiments.
5
5
  Project-URL: Documentation, https://virtualbrainlab.org/ephys_link/installation_and_use.html
6
6
  Project-URL: Issues, https://github.com/VirtualBrainLab/ephys-link/issues
@@ -31,6 +31,7 @@ Requires-Dist: platformdirs==4.2.0
31
31
  Requires-Dist: pyserial==3.5
32
32
  Requires-Dist: python-socketio==5.11.1
33
33
  Requires-Dist: pythonnet==3.0.3
34
+ Requires-Dist: requests==2.31.0
34
35
  Requires-Dist: sensapex==1.400.0
35
36
  Requires-Dist: wheel==0.42.0
36
37
  Description-Content-Type: text/markdown
@@ -86,6 +87,12 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
86
87
  is currently designed to interface with local/desktop instances of Pinpoint. It
87
88
  will not work with the web browser versions of Pinpoint at this time.
88
89
 
90
+ ## Launch from Pinpoint (Recommended)
91
+
92
+ Pinpoint comes bundled with the correct version of Ephys Link. If you are using Pinpoint on the same computer your
93
+ manipulators are connected to, you can launch the server from within Pinpoint. Follow the instructions in
94
+ the [Pinpoint documentation](https://virtualbrainlab.org/pinpoint/tutorials/tutorial_ephys_link.html#configure-and-launch-ephys-link).
95
+
89
96
  ## Install as Standalone Executable
90
97
 
91
98
  1. Download the latest executable from
@@ -49,6 +49,12 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
49
49
  is currently designed to interface with local/desktop instances of Pinpoint. It
50
50
  will not work with the web browser versions of Pinpoint at this time.
51
51
 
52
+ ## Launch from Pinpoint (Recommended)
53
+
54
+ Pinpoint comes bundled with the correct version of Ephys Link. If you are using Pinpoint on the same computer your
55
+ manipulators are connected to, you can launch the server from within Pinpoint. Follow the instructions in
56
+ the [Pinpoint documentation](https://virtualbrainlab.org/pinpoint/tutorials/tutorial_ephys_link.html#configure-and-launch-ephys-link).
57
+
52
58
  ## Install as Standalone Executable
53
59
 
54
60
  1. Download the latest executable from
@@ -35,6 +35,7 @@ dependencies = [
35
35
  "pyserial==3.5",
36
36
  "python-socketio==5.11.1",
37
37
  "pythonnet==3.0.3",
38
+ "requests==2.31.0",
38
39
  "wheel==0.42.0",
39
40
  "sensapex==1.400.0",
40
41
  ]
@@ -91,6 +92,7 @@ dependencies = [
91
92
  ]
92
93
  [tool.hatch.envs.exe.scripts]
93
94
  build = "pyinstaller.exe ephys_link.spec -y"
95
+ build_clean = "pyinstaller.exe ephys_link.spec -y --clean"
94
96
 
95
97
  [tool.coverage.run]
96
98
  source_pkgs = ["ephys_link", "tests"]
@@ -0,0 +1 @@
1
+ __version__ = "1.2.8"
@@ -1,4 +1,5 @@
1
1
  from argparse import ArgumentParser
2
+ from sys import argv
2
3
 
3
4
  from ephys_link import common as com
4
5
  from ephys_link.__about__ import __version__ as version
@@ -9,11 +10,12 @@ from ephys_link.server import Server
9
10
  # Setup argument parser.
10
11
  parser = ArgumentParser(
11
12
  description="Electrophysiology Manipulator Link: a websocket interface for"
12
- " manipulators in electrophysiology experiments",
13
+ " manipulators in electrophysiology experiments.",
13
14
  prog="python -m ephys-link",
14
15
  )
16
+ parser.add_argument("-b", "--background", dest="background", action="store_true", help="Skip configuration window.")
15
17
  parser.add_argument(
16
- "-b", "--background", dest="background", action="store_true", help="Launches in headless mode (no GUI)"
18
+ "-i", "--ignore-updates", dest="ignore_updates", action="store_true", help="Skip (ignore) checking for updates."
17
19
  )
18
20
  parser.add_argument(
19
21
  "-t",
@@ -21,23 +23,23 @@ parser.add_argument(
21
23
  type=str,
22
24
  dest="type",
23
25
  default="sensapex",
24
- help='Manipulator type (i.e. "sensapex", "new_scale", or "new_scale_pathfinder").' ' Default: "sensapex"',
26
+ help='Manipulator type (i.e. "sensapex", "new_scale", or "new_scale_pathfinder"). Default: "sensapex".',
25
27
  )
26
- parser.add_argument("-d", "--debug", dest="debug", action="store_true", help="Enable debug mode")
28
+ parser.add_argument("-d", "--debug", dest="debug", action="store_true", help="Enable debug mode.")
27
29
  parser.add_argument(
28
30
  "-p",
29
31
  "--port",
30
32
  type=int,
31
33
  default=8081,
32
34
  dest="port",
33
- help="Port to serve on. Default: 8081 (avoids conflict with other HTTP servers)",
35
+ help="Port to serve on. Default: 8081 (avoids conflict with other HTTP servers).",
34
36
  )
35
37
  parser.add_argument(
36
38
  "--pathfinder_port",
37
39
  type=int,
38
40
  default=8080,
39
41
  dest="pathfinder_port",
40
- help="Port New Scale Pathfinder's server is on. Default: 8080",
42
+ help="Port New Scale Pathfinder's server is on. Default: 8080.",
41
43
  )
42
44
  parser.add_argument(
43
45
  "-s",
@@ -46,14 +48,14 @@ parser.add_argument(
46
48
  default="no-e-stop",
47
49
  dest="serial",
48
50
  nargs="?",
49
- help="Emergency stop serial port (i.e. COM3). Default: disables emergency stop",
51
+ help="Emergency stop serial port (i.e. COM3). Default: disables emergency stop.",
50
52
  )
51
53
  parser.add_argument(
52
54
  "-v",
53
55
  "--version",
54
56
  action="version",
55
57
  version=f"Electrophysiology Manipulator Link v{version}",
56
- help="Print version and exit",
58
+ help="Print version and exit.",
57
59
  )
58
60
 
59
61
 
@@ -63,8 +65,8 @@ def main() -> None:
63
65
  # Parse arguments.
64
66
  args = parser.parse_args()
65
67
 
66
- # Launch GUI if not background.
67
- if not args.background:
68
+ # Launch GUI if there are no CLI arguments.
69
+ if len(argv) == 1:
68
70
  gui = GUI()
69
71
  gui.launch()
70
72
  return None
@@ -81,7 +83,7 @@ def main() -> None:
81
83
  e_stop.watch()
82
84
 
83
85
  # Launch with parsed arguments on main thread.
84
- server.launch(args.type, args.port, args.pathfinder_port)
86
+ server.launch(args.type, args.port, args.pathfinder_port, args.ignore_updates)
85
87
 
86
88
 
87
89
  if __name__ == "__main__":
@@ -12,6 +12,18 @@ from typing import TypedDict
12
12
  # Debugging flag
13
13
  DEBUG = False
14
14
 
15
+ # Ephys Link ASCII
16
+ ASCII = r"""
17
+ ______ _ _ _ _
18
+ | ____| | | | | (_) | |
19
+ | |__ _ __ | |__ _ _ ___ | | _ _ __ | | __
20
+ | __| | '_ \| '_ \| | | / __| | | | | '_ \| |/ /
21
+ | |____| |_) | | | | |_| \__ \ | |____| | | | | <
22
+ |______| .__/|_| |_|\__, |___/ |______|_|_| |_|_|\_\
23
+ | | __/ |
24
+ |_| |___/
25
+ """
26
+
15
27
 
16
28
  def dprint(message: str) -> None:
17
29
  """Print message if debug is enabled.
@@ -11,17 +11,30 @@ every event, the server does the following:
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- import json
15
- import sys
14
+ from json import loads
16
15
  from signal import SIGINT, SIGTERM, signal
16
+ from sys import exit
17
17
  from typing import TYPE_CHECKING, Any
18
18
 
19
- import socketio
20
19
  from aiohttp import web
21
20
  from aiohttp.web_runner import GracefulExit
22
-
23
- from ephys_link import common as com
24
- from ephys_link.__about__ import __version__ as version
21
+ from packaging import version
22
+ from requests import get
23
+ from requests.exceptions import ConnectionError
24
+ from socketio import AsyncServer
25
+
26
+ from ephys_link.__about__ import __version__
27
+ from ephys_link.common import (
28
+ ASCII,
29
+ CanWriteInputDataFormat,
30
+ DriveToDepthInputDataFormat,
31
+ DriveToDepthOutputData,
32
+ GotoPositionInputDataFormat,
33
+ InsideBrainInputDataFormat,
34
+ PositionalOutputData,
35
+ StateOutputData,
36
+ dprint,
37
+ )
25
38
  from ephys_link.platforms.new_scale_handler import NewScaleHandler
26
39
  from ephys_link.platforms.new_scale_pathfinder_handler import NewScalePathfinderHandler
27
40
  from ephys_link.platforms.sensapex_handler import SensapexHandler
@@ -34,7 +47,7 @@ if TYPE_CHECKING:
34
47
  class Server:
35
48
  def __init__(self):
36
49
  # Server and Socketio
37
- self.sio = socketio.AsyncServer()
50
+ self.sio = AsyncServer()
38
51
  self.app = web.Application()
39
52
 
40
53
  # Is there a client connected?
@@ -117,7 +130,7 @@ class Server:
117
130
  :return: Version number as defined in :mod:`ephys_link.__about__`.
118
131
  :rtype: str
119
132
  """
120
- return version
133
+ return __version__
121
134
 
122
135
  async def get_manipulators(self, _) -> str:
123
136
  """Get the list of discoverable manipulators.
@@ -127,7 +140,7 @@ class Server:
127
140
  :return: :class:`ephys_link.common.GetManipulatorsOutputData` as JSON formatted string.
128
141
  :rtype: str
129
142
  """
130
- com.dprint("[EVENT]\t\t Get discoverable manipulators")
143
+ dprint("[EVENT]\t\t Get discoverable manipulators")
131
144
 
132
145
  return self.platform.get_manipulators().json()
133
146
 
@@ -141,7 +154,7 @@ class Server:
141
154
  :return: Error message on error, empty string otherwise.
142
155
  :rtype: str
143
156
  """
144
- com.dprint(f"[EVENT]\t\t Register manipulator: {manipulator_id}")
157
+ dprint(f"[EVENT]\t\t Register manipulator: {manipulator_id}")
145
158
 
146
159
  return self.platform.register_manipulator(manipulator_id)
147
160
 
@@ -155,7 +168,7 @@ class Server:
155
168
  :return: Error message on error, empty string otherwise.
156
169
  :rtype: str
157
170
  """
158
- com.dprint(f"[EVENT]\t\t Unregister manipulator: {manipulator_id}")
171
+ dprint(f"[EVENT]\t\t Unregister manipulator: {manipulator_id}")
159
172
 
160
173
  return self.platform.unregister_manipulator(manipulator_id)
161
174
 
@@ -169,7 +182,7 @@ class Server:
169
182
  :return: :class:`ephys_link.common.PositionalOutputData` as JSON formatted string.
170
183
  :rtype: str
171
184
  """
172
- # com.dprint(f"[EVENT]\t\t Get position of manipulator" f" {manipulator_id}")
185
+ # dprint(f"[EVENT]\t\t Get position of manipulator" f" {manipulator_id}")
173
186
 
174
187
  return self.platform.get_pos(manipulator_id).json()
175
188
 
@@ -210,18 +223,18 @@ class Server:
210
223
  :rtype: str
211
224
  """
212
225
  try:
213
- parsed_data: com.GotoPositionInputDataFormat = json.loads(data)
226
+ parsed_data: GotoPositionInputDataFormat = loads(data)
214
227
  manipulator_id = parsed_data["manipulator_id"]
215
228
  pos = parsed_data["pos"]
216
229
  speed = parsed_data["speed"]
217
230
  except KeyError:
218
231
  print(f"[ERROR]\t\t Invalid goto_pos data: {data}\n")
219
- return com.PositionalOutputData([], "Invalid data format").json()
232
+ return PositionalOutputData([], "Invalid data format").json()
220
233
  except Exception as e:
221
234
  print(f"[ERROR]\t\t Error in goto_pos: {e}\n")
222
- return com.PositionalOutputData([], "Error in goto_pos").json()
235
+ return PositionalOutputData([], "Error in goto_pos").json()
223
236
  else:
224
- com.dprint(f"[EVENT]\t\t Move manipulator {manipulator_id} " f"to position {pos}")
237
+ dprint(f"[EVENT]\t\t Move manipulator {manipulator_id} " f"to position {pos}")
225
238
  goto_result = await self.platform.goto_pos(manipulator_id, pos, speed)
226
239
  return goto_result.json()
227
240
 
@@ -236,18 +249,18 @@ class Server:
236
249
  :rtype: str
237
250
  """
238
251
  try:
239
- parsed_data: com.DriveToDepthInputDataFormat = json.loads(data)
252
+ parsed_data: DriveToDepthInputDataFormat = loads(data)
240
253
  manipulator_id = parsed_data["manipulator_id"]
241
254
  depth = parsed_data["depth"]
242
255
  speed = parsed_data["speed"]
243
256
  except KeyError:
244
257
  print(f"[ERROR]\t\t Invalid drive_to_depth data: {data}\n")
245
- return com.DriveToDepthOutputData(-1, "Invalid data " "format").json()
258
+ return DriveToDepthOutputData(-1, "Invalid data " "format").json()
246
259
  except Exception as e:
247
260
  print(f"[ERROR]\t\t Error in drive_to_depth: {e}\n")
248
- return com.DriveToDepthOutputData(-1, "Error in drive_to_depth").json()
261
+ return DriveToDepthOutputData(-1, "Error in drive_to_depth").json()
249
262
  else:
250
- com.dprint(f"[EVENT]\t\t Drive manipulator {manipulator_id} to depth {depth}")
263
+ dprint(f"[EVENT]\t\t Drive manipulator {manipulator_id} to depth {depth}")
251
264
  drive_result = await self.platform.drive_to_depth(manipulator_id, depth, speed)
252
265
  return drive_result.json()
253
266
 
@@ -262,17 +275,17 @@ class Server:
262
275
  :rtype: str
263
276
  """
264
277
  try:
265
- parsed_data: com.InsideBrainInputDataFormat = json.loads(data)
278
+ parsed_data: InsideBrainInputDataFormat = loads(data)
266
279
  manipulator_id = parsed_data["manipulator_id"]
267
280
  inside = parsed_data["inside"]
268
281
  except KeyError:
269
282
  print(f"[ERROR]\t\t Invalid set_inside_brain data: {data}\n")
270
- return com.StateOutputData(False, "Invalid data format").json()
283
+ return StateOutputData(False, "Invalid data format").json()
271
284
  except Exception as e:
272
285
  print(f"[ERROR]\t\t Error in inside_brain: {e}\n")
273
- return com.StateOutputData(False, "Error in set_inside_brain").json()
286
+ return StateOutputData(False, "Error in set_inside_brain").json()
274
287
  else:
275
- com.dprint(f"[EVENT]\t\t Set manipulator {manipulator_id} inside brain to {inside}")
288
+ dprint(f"[EVENT]\t\t Set manipulator {manipulator_id} inside brain to {inside}")
276
289
  return self.platform.set_inside_brain(manipulator_id, inside).json()
277
290
 
278
291
  async def calibrate(self, _, manipulator_id: str) -> str:
@@ -285,7 +298,7 @@ class Server:
285
298
  :return: Error message on error, empty string otherwise.
286
299
  :rtype: str
287
300
  """
288
- com.dprint(f"[EVENT]\t\t Calibrate manipulator" f" {manipulator_id}")
301
+ dprint(f"[EVENT]\t\t Calibrate manipulator" f" {manipulator_id}")
289
302
 
290
303
  return await self.platform.calibrate(manipulator_id, self.sio)
291
304
 
@@ -299,7 +312,7 @@ class Server:
299
312
  :return: Error message on error, empty string otherwise.
300
313
  :rtype: str
301
314
  """
302
- com.dprint(f"[EVENT]\t\t Bypass calibration of manipulator" f" {manipulator_id}")
315
+ dprint(f"[EVENT]\t\t Bypass calibration of manipulator" f" {manipulator_id}")
303
316
 
304
317
  return self.platform.bypass_calibration(manipulator_id)
305
318
 
@@ -314,18 +327,18 @@ class Server:
314
327
  :rtype: str
315
328
  """
316
329
  try:
317
- parsed_data: com.CanWriteInputDataFormat = json.loads(data)
330
+ parsed_data: CanWriteInputDataFormat = loads(data)
318
331
  manipulator_id = parsed_data["manipulator_id"]
319
332
  can_write = parsed_data["can_write"]
320
333
  hours = parsed_data["hours"]
321
334
  except KeyError:
322
335
  print(f"[ERROR]\t\t Invalid set_can_write data: {data}\n")
323
- return com.StateOutputData(False, "Invalid data " "format").json()
336
+ return StateOutputData(False, "Invalid data " "format").json()
324
337
  except Exception as e:
325
338
  print(f"[ERROR]\t\t Error in inside_brain: {e}\n")
326
- return com.StateOutputData(False, "Error in set_can_write").json()
339
+ return StateOutputData(False, "Error in set_can_write").json()
327
340
  else:
328
- com.dprint(f"[EVENT]\t\t Set manipulator {manipulator_id} can_write state to {can_write}")
341
+ dprint(f"[EVENT]\t\t Set manipulator {manipulator_id} can_write state to {can_write}")
329
342
  return self.platform.set_can_write(manipulator_id, can_write, hours, self.sio).json()
330
343
 
331
344
  def stop(self, _) -> bool:
@@ -336,7 +349,7 @@ class Server:
336
349
  :return: True if successful, False otherwise.
337
350
  :rtype: bool
338
351
  """
339
- com.dprint("[EVENT]\t\t Stop all manipulators")
352
+ dprint("[EVENT]\t\t Stop all manipulators")
340
353
 
341
354
  return self.platform.stop()
342
355
 
@@ -356,7 +369,13 @@ class Server:
356
369
  print(f"[UNKNOWN EVENT]:\t {data}")
357
370
  return "UNKNOWN_EVENT"
358
371
 
359
- def launch(self, platform_type: str, server_port: int, pathfinder_port: int | None = None) -> None:
372
+ def launch(
373
+ self,
374
+ platform_type: str,
375
+ server_port: int,
376
+ pathfinder_port: int | None = None,
377
+ ignore_updates: bool = False, # noqa: FBT002
378
+ ) -> None:
360
379
  """Launch the server.
361
380
 
362
381
  :param platform_type: Parsed argument for platform type.
@@ -365,6 +384,8 @@ class Server:
365
384
  :type server_port: int
366
385
  :param pathfinder_port: Port New Scale Pathfinder's server is on.
367
386
  :type pathfinder_port: int
387
+ :param ignore_updates: Flag to ignore checking for updates.
388
+ :type ignore_updates: bool
368
389
  :return: None
369
390
  """
370
391
 
@@ -379,16 +400,34 @@ class Server:
379
400
  elif platform_type == "new_scale_pathfinder":
380
401
  self.platform = NewScalePathfinderHandler(pathfinder_port)
381
402
  else:
382
- sys.exit(f"[ERROR]\t\t Invalid manipulator type: {platform_type}")
383
-
384
- # Preamble
385
- print(f"=== Ephys Link v{version} ===")
403
+ exit(f"[ERROR]\t\t Invalid manipulator type: {platform_type}")
404
+
405
+ # Preamble.
406
+ print(ASCII)
407
+ print(f"v{__version__}")
408
+
409
+ # Check for newer version.
410
+ if not ignore_updates:
411
+ try:
412
+ version_request = get("https://api.github.com/repos/VirtualBrainLab/ephys-link/tags", timeout=10)
413
+ latest_version = version_request.json()[0]["name"]
414
+ if version.parse(latest_version) > version.parse(__version__):
415
+ print(f"New version available: {latest_version}")
416
+ print("Download at: https://github.com/VirtualBrainLab/ephys-link/releases/latest")
417
+ except ConnectionError:
418
+ pass
419
+
420
+ # Explain window.
421
+ print()
422
+ print("This is the Ephys Link server window.")
423
+ print("You may safely leave it running in the background.")
424
+ print("To stop the it, close this window or press CTRL + Pause/Break.")
425
+ print()
386
426
 
387
427
  # List available manipulators
388
428
  print("Available Manipulators:")
389
429
  print(self.platform.get_manipulators()["manipulators"])
390
-
391
- print("\n(Shutdown server with CTRL+Pause/Break)\n")
430
+ print()
392
431
 
393
432
  # Mark that server is running
394
433
  self.is_running = True
@@ -1 +0,0 @@
1
- __version__ = "1.2.5"
File without changes
File without changes
File without changes
File without changes
File without changes