diff --git a/README.md b/README.md index 7a0622d..8c2a905 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,7 @@ server.launch("sensapex", args.proxy_address, 8081) 1. Clone the repository. 2. Install [Hatch](https://hatch.pypa.io/latest/install/) -3. Install the latest Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11) - via [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/). -4. In a terminal, navigate to the repository's root directory and run +3. In a terminal, navigate to the repository's root directory and run ```bash hatch shell @@ -103,6 +101,10 @@ server.launch("sensapex", args.proxy_address, 8081) This will create a virtual environment, install Python 12 (if not found), and install the package in editable mode. +If you encounter any dependency issues (particularly with `aiohttp`), try installing the latest Microsoft Visual C++ +(MSVC v143+ x86/64) and the Windows SDK (10/11) +via [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/). + # Documentation and More Information Complete documentation including API usage and development installation can be diff --git a/ephys_link.spec b/ephys_link.spec index f7b14d5..ad3bc08 100644 --- a/ephys_link.spec +++ b/ephys_link.spec @@ -8,6 +8,8 @@ parser = ArgumentParser() parser.add_argument("-d", "--dir", action="store_true", help="Outputs a directory") options = parser.parse_args() +FILE_NAME = f"EphysLink-v{version}" + a = Analysis( ['src\\ephys_link\\__main__.py'], pathex=[], @@ -19,30 +21,50 @@ a = Analysis( runtime_hooks=[], excludes=[], noarchive=False, + optimize=1, ) pyz = PYZ(a.pure) -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, - [], - name=f"EphysLink-v{version}", - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, - icon='assets\\icon.ico', -) - if options.dir: - coll = COLLECT(exe, a.binaries, name=f"EphysLink-v{version}") + exe = EXE( + pyz, + a.scripts, + [], + exlude_binaries=True, + name=FILE_NAME, + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='assets\\icon.ico', + ) + coll = COLLECT(exe, a.binaries, a.datas, strip=False, upx=True, upx_exclude=[], name=FILE_NAME) +else: + exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name=FILE_NAME, + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='assets\\icon.ico', + ) diff --git a/pyproject.toml b/pyproject.toml index efeee4d..1189062 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ dependencies = [ "pyinstaller", ] [tool.hatch.envs.exe.scripts] -build = "pyinstaller.exe ephys_link.spec -y -- -d" +build = "pyinstaller.exe ephys_link.spec -y -- -d && pyinstaller.exe ephys_link.spec -y" build_onefile = "pyinstaller.exe ephys_link.spec -y" build_clean = "pyinstaller.exe ephys_link.spec -y --clean" diff --git a/src/ephys_link/gui.py b/src/ephys_link/gui.py index de2076e..e60c7b4 100644 --- a/src/ephys_link/gui.py +++ b/src/ephys_link/gui.py @@ -1,3 +1,4 @@ +from asyncio import run from json import dumps, load from os import makedirs from os.path import exists @@ -24,15 +25,27 @@ def __init__(self) -> None: self._root = Tk() # Create default settings dictionary - settings = {"type": "sensapex", "debug": False, "port": 8081, "pathfinder_port": 8080, "serial": "no-e-stop"} + settings = { + "ignore_updates": False, + "type": "sensapex", + "debug": False, + "proxy": False, + "proxy_address": "proxy2.virtualbrainlab.org", + "port": 8081, + "pathfinder_port": 8080, + "serial": "no-e-stop", + } # Read settings. if exists(f"{SETTINGS_DIR}\\{SETTINGS_FILENAME}"): with open(f"{SETTINGS_DIR}\\{SETTINGS_FILENAME}") as settings_file: settings = load(settings_file) + self._ignore_updates = BooleanVar(value=settings["ignore_updates"]) self._type = StringVar(value=settings["type"]) self._debug = BooleanVar(value=settings["debug"]) + self._proxy = BooleanVar(value=settings["proxy"]) + self._proxy_address = StringVar(value=settings["proxy_address"]) self._port = IntVar(value=settings["port"]) self._pathfinder_port = IntVar(value=settings["pathfinder_port"]) self._serial = StringVar(value=settings["serial"]) @@ -61,15 +74,41 @@ def _build_gui(self): server_serving_settings = ttk.LabelFrame(mainframe, text="Serving Settings", padding=3) server_serving_settings.grid(column=0, row=0, sticky="news") - # IP. - ttk.Label(server_serving_settings, text="IP:", anchor=E, justify=RIGHT).grid(column=0, row=0, sticky="we") + # Local IP. + ttk.Label(server_serving_settings, text="Local IP:", anchor=E, justify=RIGHT).grid(column=0, row=0, sticky="we") ttk.Label(server_serving_settings, text=gethostbyname(gethostname())).grid(column=1, row=0, sticky="we") + # Proxy. + ttk.Label(server_serving_settings, text="Use Proxy:", anchor=E, justify=RIGHT).grid( + column=0, row=1, sticky="we" + ) + ttk.Checkbutton( + server_serving_settings, + variable=self._proxy, + ).grid(column=1, row=1, sticky="we") + + # Proxy address. + ttk.Label(server_serving_settings, text="Proxy Address:", anchor=E, justify=RIGHT).grid( + column=0, row=2, sticky="we" + ) + ttk.Entry(server_serving_settings, textvariable=self._proxy_address, justify=CENTER).grid( + column=1, row=2, sticky="we" + ) + # Port. - ttk.Label(server_serving_settings, text="Port:", anchor=E, justify=RIGHT).grid(column=0, row=1, sticky="we") + ttk.Label(server_serving_settings, text="Port:", anchor=E, justify=RIGHT).grid(column=0, row=3, sticky="we") ttk.Entry(server_serving_settings, textvariable=self._port, width=5, justify=CENTER).grid( - column=1, row=1, sticky="we" + column=1, row=3, sticky="we" + ) + + # Ignore updates. + ttk.Label(server_serving_settings, text="Ignore Updates:", anchor=E, justify=RIGHT).grid( + column=0, row=4, sticky="we" ) + ttk.Checkbutton( + server_serving_settings, + variable=self._ignore_updates, + ).grid(column=1, row=4, sticky="we") # --- @@ -141,8 +180,11 @@ def _launch_server(self) -> None: # Save settings. settings = { + "ignore_updates": self._ignore_updates.get(), "type": self._type.get(), "debug": self._debug.get(), + "proxy": self._proxy.get(), + "proxy_address": self._proxy_address.get(), "port": self._port.get(), "pathfinder_port": self._pathfinder_port.get(), "serial": self._serial.get(), @@ -160,4 +202,16 @@ def _launch_server(self) -> None: e_stop = EmergencyStop(server, self._serial.get()) e_stop.watch() - server.launch(self._type.get(), self._port.get(), self._pathfinder_port.get()) + # Launch with parsed arguments on main thread. + if self._proxy.get(): + run( + server.launch_for_proxy( + self._proxy_address.get(), + self._port.get(), + self._type.get(), + self._pathfinder_port.get(), + self._ignore_updates.get(), + ) + ) + else: + server.launch(self._type.get(), self._port.get(), self._pathfinder_port.get(), self._ignore_updates.get()) diff --git a/src/ephys_link/server.py b/src/ephys_link/server.py index ebda8e5..eb5660c 100644 --- a/src/ephys_link/server.py +++ b/src/ephys_link/server.py @@ -12,7 +12,7 @@ from __future__ import annotations from asyncio import get_event_loop -from json import dumps, loads +from json import loads from signal import SIGINT, SIGTERM, signal from typing import TYPE_CHECKING, Any from uuid import uuid4 @@ -34,6 +34,7 @@ InsideBrainRequest, PositionalResponse, ) +from vbl_aquarium.models.proxy import PinpointIdResponse from ephys_link.__about__ import __version__ from ephys_link.common import ( @@ -117,7 +118,7 @@ async def get_pinpoint_id(self) -> str: :return: Pinpoint ID and whether the client is a requester. :rtype: tuple[str, bool] """ - return dumps({"pinpoint_id": self.pinpoint_id, "is_requester": False}) + return PinpointIdResponse(pinpoint_id=self.pinpoint_id, is_requester=False).to_string() @staticmethod async def get_version(_) -> str: