Skip to content

Commit 9e9f498

Browse files
authored
286 launch gui (#287)
* Extract serial to e stop class * Simplified main * Privatized fields, reworking gui script * Moved signal to server, launched GUI * Add all manipulator options * Save settings * Version bump * Update README for gui and exe * Updated README, working on exe * Use single EXE with versioned name * Add icon to EXE * Update launch language * Fixed optional annotation * Add build action to test building * Use windows host for build * Add build cache * Add build badge
1 parent 3591a0b commit 9e9f498

File tree

10 files changed

+295
-244
lines changed

10 files changed

+295
-244
lines changed

.github/workflows/build.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Build
2+
3+
on:
4+
pull_request:
5+
merge_group:
6+
7+
jobs:
8+
build:
9+
name: Build
10+
runs-on: windows-latest
11+
12+
steps:
13+
- name: 🛎 Checkout
14+
uses: actions/checkout@v4
15+
with:
16+
ref: ${{ github.head_ref }}
17+
18+
- name: 🐍 Setup Python
19+
uses: actions/setup-python@v4
20+
with:
21+
python-version: '3.12'
22+
cache: 'pip'
23+
24+
- name: 📦 Install Hatch
25+
run: pip install hatch
26+
27+
- name: 🗃️ Cache Build Artifacts
28+
uses: actions/cache@v4
29+
with:
30+
path: build
31+
key: ${{ runner.os }}-build
32+
33+
- name: 🔨 Build
34+
run: hatch -e exe run build

README.md

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Electrophysiology Manipulator Link
22

33
[![PyPI version](https://badge.fury.io/py/ephys-link.svg)](https://badge.fury.io/py/ephys-link)
4+
[![Build](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml)
45
[![CodeQL](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml)
56
[![Dependency Review](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml)
67
[![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch)
@@ -33,17 +34,12 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
3334

3435
## Prerequisites
3536

36-
1. [Python ≥ 3.8, < 3.13](https://www.python.org/downloads/release/python-3116/)
37-
1. Python 3.12+ requires the latest version
38-
of Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11) to
39-
be installed. They can be acquired through
40-
the [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
41-
2. An **x86 Windows PC is required** to run the server.
42-
3. For Sensapex devices, the controller unit must be connected via an ethernet
37+
1. An **x86 Windows PC is required** to run the server.
38+
2. For Sensapex devices, the controller unit must be connected via an ethernet
4339
cable and powered. A USB-to-ethernet adapter is acceptable. For New Scale manipulators,
4440
the controller unit must be connected via USB and be powered by a 6V power
4541
supply.
46-
4. To use the emergency stop feature, ensure an Arduino with
42+
3. To use the emergency stop feature, ensure an Arduino with
4743
the [StopSignal](https://github.com/VirtualBrainLab/StopSignal) sketch is
4844
connected to the computer. Follow the instructions on that repo for how to
4945
set up the Arduino.
@@ -52,59 +48,70 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
5248
is currently designed to interface with local/desktop instances of Pinpoint. It
5349
will not work with the web browser versions of Pinpoint at this time.
5450

55-
<div style="padding: 15px; border: 1px solid transparent; border-color: transparent; margin-bottom: 20px; border-radius: 4px; color: #31708f; background-color: #d9edf7; border-color: #bce8f1;">
56-
<h3>Using a Python virtual environment is encouraged.</h3>
57-
<p>Create a virtual environment by running <code>python -m venv ephys_link</code></p>
58-
<p>Activate the environment by running <code>.\ephys_link\scripts\activate</code></p>
59-
<p>A virtual environment helps to isolate installed packages from other packages on your computer and ensures a clean installation of Ephys Link</p>
60-
</div>
51+
## Install as Standalone Executable
6152

62-
## Install for use
53+
1. Download the latest executable from
54+
the [releases page](https://github.com/VirtualBrainLab/ephys-link/releases/latest).
55+
2. Double-click the executable file to launch the configuration window.
56+
1. Take note of the IP address and port. **Copy this information into Pinpoint to connect**.
57+
3. Select the desired configuration and click "Launch Server".
6358

64-
Run the following command to install the server:
59+
The configuration window will close and the server will launch. Your configurations will be saved for future use.
6560

66-
```bash
67-
pip install ephys-link
68-
```
61+
To connect to the server from Pinpoint, provide the IP address and port. For example, if the server is running on the
62+
same computer that Pinpoint is, use
6963

70-
Update the server like any other Python package:
64+
- Server: `localhost`
65+
- Port: `8081`
7166

72-
```bash
73-
pip install --upgrade ephys-link
74-
```
67+
If the server is running on a different (local) computer, use the IP address of that computer as shown in the startup
68+
window instead of `localhost`.
7569

76-
## Install for development
70+
## Install for Development
7771

7872
1. Clone the repository.
7973
2. Install [Hatch](https://hatch.pypa.io/latest/install/)
80-
3. In a terminal, navigate to the repository's root directory and run
74+
3. Install the latest Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11)
75+
via [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
76+
4. In a terminal, navigate to the repository's root directory and run
8177

8278
```bash
83-
hatch shell
79+
hatch shell
8480
```
8581

86-
This will create a virtual environment and install the package in editable mode.
82+
This will create a virtual environment, install Python 12 (if not found), and install the package in editable mode.
83+
84+
## Install as a Python package
8785

88-
# Usage
86+
```bash
87+
pip install ephys-link
88+
```
89+
90+
Import the modules you need and launch the server.
8991

90-
Run the following commands in a terminal to start the server for the desired manipulator platform:
92+
```python
93+
from ephys_link.server import Server
9194

92-
| Manipulator Platform | Command |
93-
|--------------------------------------|--------------------------------------|
94-
| Sensapex uMp-4 | `ephys-link` |
95-
| Sensapex uMp-3 | `ephys-link -t ump3` |
96-
| New Scale | `ephys-link -t new_scale` |
97-
| New Scale via Pathfinder HTTP server | `ephys-link -t new_scale_pathfinder` |
95+
server = Server()
96+
server.launch("sensapex", 8081)
97+
```
9898

99-
There are a couple additional aliases for the Ephys Link executable: `ephys_link` and `el`.
99+
# CLI Usage
100100

101-
By default, the server will broadcast with its local IP address on port 8081.
102-
**Copy this information into Pinpoint to connect**.
101+
Ephys Link can be launched from the command line directly. This is useful for computers or servers without graphical
102+
user interfaces.
103103

104-
For example, if the server is running on the same computer that Pinpoint is, use
104+
Run the following commands in a terminal to start the server for the desired manipulator platform without the startup
105+
window:
105106

106-
- Server: `localhost`
107-
- Port: `8081`
107+
| Manipulator Platform | Command |
108+
|--------------------------------------|---------------------------------------------|
109+
| Sensapex uMp-4 | `ephys-link.exe -b` |
110+
| Sensapex uMp-3 | `ephys-link.exe -b -t ump3` |
111+
| New Scale | `ephys-link.exe -b -t new_scale` |
112+
| New Scale via Pathfinder HTTP server | `ephys-link.exe -b -t new_scale_pathfinder` |
113+
114+
More options can be viewed by running `ephys-link.exe -h`.
108115

109116
# Documentation and More Information
110117

assets/icon.ico

66.1 KB
Binary file not shown.

ephys_link.spec

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- mode: python ; coding: utf-8 -*-
22

3+
from ephys_link.__about__ import __version__ as version
34

45
a = Analysis(
56
['src\\ephys_link\\__main__.py'],
@@ -18,26 +19,21 @@ pyz = PYZ(a.pure)
1819
exe = EXE(
1920
pyz,
2021
a.scripts,
22+
a.binaries,
23+
a.datas,
2124
[],
22-
exclude_binaries=True,
23-
name='ephys_link',
25+
name=f"ephys_link-v{version}-Windows-x86_64",
2426
debug=False,
2527
bootloader_ignore_signals=False,
2628
strip=False,
2729
upx=True,
30+
upx_exclude=[],
31+
runtime_tmpdir=None,
2832
console=True,
2933
disable_windowed_traceback=False,
3034
argv_emulation=False,
3135
target_arch=None,
3236
codesign_identity=None,
3337
entitlements_file=None,
34-
)
35-
coll = COLLECT(
36-
exe,
37-
a.binaries,
38-
a.datas,
39-
strip=False,
40-
upx=True,
41-
upx_exclude=[],
42-
name='ephys_link',
38+
icon='assets\\icon.ico',
4339
)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ classifiers = [
3131
]
3232
dependencies = [
3333
"aiohttp==3.9.1",
34+
"platformdirs==4.1.0",
3435
"pyserial==3.5",
3536
"python-socketio==5.11.0",
3637
"pythonnet==3.0.3",

src/ephys_link/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.1.1"
1+
__version__ = "1.2.0"

src/ephys_link/__main__.py

Lines changed: 25 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,20 @@
1-
import argparse
2-
import signal
3-
import time
4-
from threading import Event, Thread
5-
6-
import serial
7-
import serial.tools.list_ports as ports
1+
from argparse import ArgumentParser
82

93
from ephys_link import common as com
104
from ephys_link.__about__ import __version__ as version
5+
from ephys_link.emergency_stop import EmergencyStop
6+
from ephys_link.gui import GUI
117
from ephys_link.server import Server
128

13-
# Setup Arduino serial port (emergency stop)
14-
poll_rate = 0.05
15-
kill_serial_event = Event()
16-
poll_serial_thread: Thread
17-
18-
# Create Server
19-
server = Server()
20-
21-
22-
def poll_serial(kill_event: Event, serial_port: str) -> None:
23-
"""Continuously poll serial port for data
24-
25-
:param kill_event: Event to stop polling
26-
:type kill_event: Event
27-
:param serial_port: The serial port to poll
28-
:type serial_port: str
29-
:return: None
30-
"""
31-
target_port = serial_port
32-
if serial_port is None:
33-
# Search for serial ports
34-
for port, desc, _ in ports.comports():
35-
if "Arduino" in desc or "USB Serial Device" in desc:
36-
target_port = port
37-
break
38-
elif serial_port == "no-e-stop":
39-
# Stop polling if no-e-stop is specified
40-
return None
41-
42-
ser = serial.Serial(target_port, 9600, timeout=poll_rate)
43-
while not kill_event.is_set():
44-
if ser.in_waiting > 0:
45-
ser.readline()
46-
# Cause a break
47-
com.dprint("[EMERGENCY STOP]\t\t Stopping all manipulators")
48-
server.platform.stop()
49-
ser.reset_input_buffer()
50-
time.sleep(poll_rate)
51-
print("Close poll")
52-
ser.close()
53-
54-
55-
def close_serial(_, __) -> None:
56-
"""Close the serial connection"""
57-
print("[INFO]\t\t Closing serial")
58-
kill_serial_event.set()
59-
poll_serial_thread.join()
60-
61-
62-
# Setup argument parser
63-
parser = argparse.ArgumentParser(
9+
# Setup argument parser.
10+
parser = ArgumentParser(
6411
description="Electrophysiology Manipulator Link: a websocket interface for"
6512
" manipulators in electrophysiology experiments",
6613
prog="python -m ephys-link",
6714
)
68-
# parser.add_argument("-g", "--gui", dest="gui", action="store_true", help="Launches GUI")
15+
parser.add_argument(
16+
"-b", "--background", dest="background", action="store_true", help="Launches in headless mode (no GUI)"
17+
)
6918
parser.add_argument(
7019
"-t",
7120
"--type",
@@ -111,34 +60,28 @@ def close_serial(_, __) -> None:
11160
def main() -> None:
11261
"""Main function"""
11362

114-
# Parse arguments
63+
# Parse arguments.
11564
args = parser.parse_args()
116-
com.DEBUG = args.debug
11765

118-
# Setup serial port
119-
if args.serial != "no-e-stop":
120-
# Register serial exit
121-
signal.signal(signal.SIGTERM, close_serial)
122-
signal.signal(signal.SIGINT, close_serial)
66+
# Launch GUI if not background.
67+
if not args.background:
68+
gui = GUI()
69+
gui.launch()
70+
return None
71+
72+
# Otherwise, create Server from CLI.
73+
server = Server()
12374

124-
# Start emergency stop system if serial is provided
125-
global poll_serial_thread
126-
poll_serial_thread = Thread(
127-
target=poll_serial,
128-
args=(
129-
kill_serial_event,
130-
args.serial,
131-
),
132-
daemon=True,
133-
)
134-
poll_serial_thread.start()
75+
# Continue with CLI if not.
76+
com.DEBUG = args.debug
13577

136-
# Register server exit
137-
signal.signal(signal.SIGTERM, server.close_server)
138-
signal.signal(signal.SIGINT, server.close_server)
78+
# Setup serial port.
79+
if args.serial != "no-e-stop":
80+
e_stop = EmergencyStop(server, args.serial)
81+
e_stop.watch()
13982

140-
# Launch with parsed arguments on main thread
141-
server.launch_server(args.type, args.port, args.pathfinder_port)
83+
# Launch with parsed arguments on main thread.
84+
server.launch(args.type, args.port, args.pathfinder_port)
14285

14386

14487
if __name__ == "__main__":

0 commit comments

Comments
 (0)