python-mister-fpga 0.2.0__tar.gz → 0.3.1__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 (39) hide show
  1. python_mister_fpga-0.3.1/CHANGELOG.md +42 -0
  2. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/PKG-INFO +30 -2
  3. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/README.md +28 -0
  4. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/docs/api.md +20 -0
  5. python_mister_fpga-0.3.1/docs/changelog.md +40 -0
  6. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/docs/usage.md +29 -0
  7. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/pyproject.toml +1 -1
  8. python_mister_fpga-0.3.1/src/mister_fpga/__init__.py +97 -0
  9. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/src/mister_fpga/client.py +174 -20
  10. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/src/mister_fpga/const.py +47 -2
  11. python_mister_fpga-0.3.1/src/mister_fpga/gamepad.py +127 -0
  12. python_mister_fpga-0.3.1/src/mister_fpga/ra.py +171 -0
  13. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/src/mister_fpga/ssh.py +97 -7
  14. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/src/mister_fpga/websocket.py +95 -13
  15. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/tests/test_client.py +85 -6
  16. python_mister_fpga-0.3.1/tests/test_gamepad_ralog.py +44 -0
  17. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/tests/test_ra.py +1 -1
  18. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/tests/test_ra_web.py +1 -1
  19. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/tests/test_ssh.py +41 -0
  20. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/tests/test_websocket.py +33 -0
  21. python_mister_fpga-0.2.0/CHANGELOG.md +0 -15
  22. python_mister_fpga-0.2.0/docs/changelog.md +0 -13
  23. python_mister_fpga-0.2.0/src/mister_fpga/__init__.py +0 -52
  24. python_mister_fpga-0.2.0/src/mister_fpga/ra.py +0 -82
  25. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/.github/workflows/docs.yml +0 -0
  26. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/.github/workflows/release.yml +0 -0
  27. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/.github/workflows/tests.yml +0 -0
  28. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/.gitignore +0 -0
  29. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/LICENSE +0 -0
  30. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/assets/dark_logo.png +0 -0
  31. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/assets/icon.png +0 -0
  32. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/assets/logo.png +0 -0
  33. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/docs/assets/dark_logo.png +0 -0
  34. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/docs/assets/icon.png +0 -0
  35. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/docs/assets/logo.png +0 -0
  36. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/docs/index.md +0 -0
  37. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/mkdocs.yml +0 -0
  38. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/src/mister_fpga/py.typed +0 -0
  39. {python_mister_fpga-0.2.0 → python_mister_fpga-0.3.1}/src/mister_fpga/ra_web.py +0 -0
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ ## 0.3.1
4
+ - Import `asyncssh` at module load. The lazy import inside `MisterSSH._ensure` ran on the event loop and made Home Assistant report blocking calls (liboqs/fido2 file reads) on the first SSH connection.
5
+
6
+ ## 0.3.0
7
+
8
+ Findings from reverse-engineering mrext Remote 0.4/0.5 and odelot's Main_MiSTer on a real device.
9
+
10
+ **Fixes**
11
+ - `async_search_games` sent `data`; the server reads `query`, so every search returned the whole index. Adds `page` (Remote 0.5 paginates). Response is `{data, total, pageSize, page}`.
12
+ - `async_set_background_mode` used `/settings/core/menu` (answered by the web UI); the route is `/settings/cores/menu`, and only modes 0, 2, 4 … 14 are valid (`BACKGROUND_MODES`). Invalid modes now raise `ValueError`.
13
+ - `async_launch_token` now base64url-encodes the token as `/l/` requires; pass plain text such as `**random:snes`.
14
+ - Unknown routes (Remote answers them with `200 text/html`) raise `MisterUnsupportedError` instead of "invalid JSON".
15
+ - `async_get_screenshots` returns `[]` when the server sends `null`.
16
+ - Path segments (screenshots, wallpapers, scripts, playlists) are URL-quoted.
17
+ - `MisterStatus` disk fields come from the `/media/fat` entry, not just the first disk.
18
+ - WebSocket reducer: `menuNavigation:FULL:CURRENT` is split and joined into one path; arcade `gameRunning:<core>` keeps the name; a new `coreRunning` clears the stale system/game.
19
+ - Docstrings: `game` is `<system>/<file>`, not a path; `dns` is the mDNS name; raw keyboard codes are Linux evdev codes.
20
+
21
+ **New**
22
+ - Main health over SSH: `parse_ssh_probe` adds `cmd_fifo_pending` (bytes Main has not read from `/dev/MiSTer_cmd` — non-zero on two probes means Main is hung), `main_open_fds`, `volume`/`muted`, `game_id`, `sam_active`, `sam_game`.
23
+ - `MisterSSH.async_send_command` writes the command FIFO non-blocking and raises `MisterMainUnresponsiveError` instead of hanging; `async_set_volume`, `async_set_mute`.
24
+ - `MisterGamepad`: virtual controller/keyboard through the MiSTer Companion Remote daemon (port 9191).
25
+ - `parse_ra_log` / `MisterRA.async_read_log` / `MisterRA.async_set_debug`: RetroAchievements login, game identified, achievement, leaderboard, game-completed events from odelot's debug log.
26
+ - `parse_index_status` / `MisterIndexStatus` (index progress), `split_menu_navigation`, `MisterWebSocketClient.async_send` (e.g. `kbdRawDown:`/`kbdRawUp:`).
27
+ - REST: `async_delete_screenshot`, `async_get_search_systems`, `async_browse_games`, `async_browse_menu` (rejects `..`), `async_get_remote_log`, and for Remote 0.5 `async_get_screen`, `async_get_playlog_status/summary/games/sessions`; `MisterStatus.core_session_time`/`game_session_time`, `disks`.
28
+ - `MDNS_SERVICE_TYPE` for zeroconf discovery.
29
+
30
+ ## 0.2.0
31
+ - Add `MisterRAWeb` — async client for the [RetroAchievements.org Web API](https://api.docs.retroachievements.org/) to pull cloud player stats (points, rank, recently-played games, latest unlocked achievement) without SSH.
32
+ - New dataclasses `MisterRAWebStats`, `RAGameProgress`, `RAAchievement` for aggregated cloud-stats snapshots.
33
+ - New exception `MisterRAWebError` raised on network, HTTP, or API-level failures (including bad credentials).
34
+
35
+ ## 0.1.2
36
+ - Add online documentation (MkDocs Material) at https://hudsonbrendon.github.io/python-mister-fpga/ and enrich public-API docstrings.
37
+
38
+ ## 0.1.1
39
+ - Export `DEFAULT_SSH_PORT`, `DEFAULT_SSH_USERNAME` and `SSH_PROBE_CMD` from the top-level package so consumers can reuse them without duplicating.
40
+
41
+ ## 0.1.0
42
+ - Initial release: async REST client (`MisterClient`/`MisterStatus`/`MisterConnectionError`), WebSocket reducer + client (`apply_ws_message`/`MisterWebSocketClient`), and SSH telemetry (`parse_ssh_probe`/`MisterSSH`) for the MiSTer FPGA mrext Remote API.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: python-mister-fpga
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Async Python client for the MiSTer FPGA mrext Remote API (REST + WebSocket) and SSH telemetry
5
5
  Project-URL: Homepage, https://github.com/hudsonbrendon/python-mister-fpga
6
6
  Project-URL: Documentation, https://hudsonbrendon.github.io/python-mister-fpga/
@@ -108,6 +108,34 @@ async def main():
108
108
  asyncio.run(main())
109
109
  ```
110
110
 
111
+ ### Main health, volume and Super Attract Mode (SSH)
112
+
113
+ ```python
114
+ data = await ssh.async_probe()
115
+ data["cmd_fifo_pending"] # >0 on two probes in a row: Main is hung
116
+ data["volume"], data["muted"], data["game_id"], data["sam_game"]
117
+ await ssh.async_set_volume(5) # 0-7, never blocks on a hung Main
118
+ await ssh.async_send_command("load_core /media/fat/menu.rbf")
119
+ ```
120
+
121
+ ### Virtual gamepad (MiSTer Companion Remote daemon, port 9191)
122
+
123
+ ```python
124
+ from mister_fpga import MisterGamepad
125
+
126
+ pad = MisterGamepad("192.168.1.50")
127
+ await pad.async_press("start") # a real controller press: works in cores, menu, SAM
128
+ ```
129
+
130
+ ### RetroAchievements events (odelot Main with `debug=1`)
131
+
132
+ ```python
133
+ from mister_fpga import MisterRA
134
+
135
+ ra = MisterRA(ssh)
136
+ offset, events = await ra.async_read_log(0) # login / game_identified / achievement / ...
137
+ ```
138
+
111
139
  ## RetroAchievements cloud stats
112
140
 
113
141
  `MisterRAWeb` queries the public [RetroAchievements.org Web API](https://api.docs.retroachievements.org/) to pull a player's points, rank, recently-played games, and latest unlocked achievement — no SSH required. You need a **Web API key** from [retroachievements.org/settings](https://retroachievements.org/settings).
@@ -76,6 +76,34 @@ async def main():
76
76
  asyncio.run(main())
77
77
  ```
78
78
 
79
+ ### Main health, volume and Super Attract Mode (SSH)
80
+
81
+ ```python
82
+ data = await ssh.async_probe()
83
+ data["cmd_fifo_pending"] # >0 on two probes in a row: Main is hung
84
+ data["volume"], data["muted"], data["game_id"], data["sam_game"]
85
+ await ssh.async_set_volume(5) # 0-7, never blocks on a hung Main
86
+ await ssh.async_send_command("load_core /media/fat/menu.rbf")
87
+ ```
88
+
89
+ ### Virtual gamepad (MiSTer Companion Remote daemon, port 9191)
90
+
91
+ ```python
92
+ from mister_fpga import MisterGamepad
93
+
94
+ pad = MisterGamepad("192.168.1.50")
95
+ await pad.async_press("start") # a real controller press: works in cores, menu, SAM
96
+ ```
97
+
98
+ ### RetroAchievements events (odelot Main with `debug=1`)
99
+
100
+ ```python
101
+ from mister_fpga import MisterRA
102
+
103
+ ra = MisterRA(ssh)
104
+ offset, events = await ra.async_read_log(0) # login / game_identified / achievement / ...
105
+ ```
106
+
79
107
  ## RetroAchievements cloud stats
80
108
 
81
109
  `MisterRAWeb` queries the public [RetroAchievements.org Web API](https://api.docs.retroachievements.org/) to pull a player's points, rank, recently-played games, and latest unlocked achievement — no SSH required. You need a **Web API key** from [retroachievements.org/settings](https://retroachievements.org/settings).
@@ -14,6 +14,26 @@
14
14
 
15
15
  ::: mister_fpga.MisterConnectionError
16
16
 
17
+ ::: mister_fpga.MisterUnsupportedError
18
+
19
+ ::: mister_fpga.MisterMainUnresponsiveError
20
+
21
+ ::: mister_fpga.MisterGamepad
22
+
23
+ ::: mister_fpga.MisterGamepadError
24
+
25
+ ::: mister_fpga.MisterIndexStatus
26
+
27
+ ::: mister_fpga.parse_index_status
28
+
29
+ ::: mister_fpga.split_menu_navigation
30
+
31
+ ::: mister_fpga.parse_volume
32
+
33
+ ::: mister_fpga.parse_ra_log
34
+
35
+ ::: mister_fpga.MisterRA
36
+
17
37
  ::: mister_fpga.MisterRAWeb
18
38
 
19
39
  ::: mister_fpga.MisterRAWebStats
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## 0.3.1
4
+ - Import `asyncssh` at module load. The lazy import inside `MisterSSH._ensure` ran on the event loop and made Home Assistant report blocking calls (liboqs/fido2 file reads) on the first SSH connection.
5
+
6
+ ## 0.3.0
7
+
8
+ Findings from reverse-engineering mrext Remote 0.4/0.5 and odelot's Main_MiSTer on a real device.
9
+
10
+ **Fixes**
11
+ - `async_search_games` sent `data`; the server reads `query`, so every search returned the whole index. Adds `page` (Remote 0.5 paginates). Response is `{data, total, pageSize, page}`.
12
+ - `async_set_background_mode` used `/settings/core/menu` (answered by the web UI); the route is `/settings/cores/menu`, and only modes 0, 2, 4 … 14 are valid (`BACKGROUND_MODES`). Invalid modes now raise `ValueError`.
13
+ - `async_launch_token` now base64url-encodes the token as `/l/` requires; pass plain text such as `**random:snes`.
14
+ - Unknown routes (Remote answers them with `200 text/html`) raise `MisterUnsupportedError` instead of "invalid JSON".
15
+ - `async_get_screenshots` returns `[]` when the server sends `null`.
16
+ - Path segments (screenshots, wallpapers, scripts, playlists) are URL-quoted.
17
+ - `MisterStatus` disk fields come from the `/media/fat` entry, not just the first disk.
18
+ - WebSocket reducer: `menuNavigation:FULL:CURRENT` is split and joined into one path; arcade `gameRunning:<core>` keeps the name; a new `coreRunning` clears the stale system/game.
19
+ - Docstrings: `game` is `<system>/<file>`, not a path; `dns` is the mDNS name; raw keyboard codes are Linux evdev codes.
20
+
21
+ **New**
22
+ - Main health over SSH: `parse_ssh_probe` adds `cmd_fifo_pending` (bytes Main has not read from `/dev/MiSTer_cmd` — non-zero on two probes means Main is hung), `main_open_fds`, `volume`/`muted`, `game_id`, `sam_active`, `sam_game`.
23
+ - `MisterSSH.async_send_command` writes the command FIFO non-blocking and raises `MisterMainUnresponsiveError` instead of hanging; `async_set_volume`, `async_set_mute`.
24
+ - `MisterGamepad`: virtual controller/keyboard through the MiSTer Companion Remote daemon (port 9191).
25
+ - `parse_ra_log` / `MisterRA.async_read_log` / `MisterRA.async_set_debug`: RetroAchievements login, game identified, achievement, leaderboard, game-completed events from odelot's debug log.
26
+ - `parse_index_status` / `MisterIndexStatus` (index progress), `split_menu_navigation`, `MisterWebSocketClient.async_send` (e.g. `kbdRawDown:`/`kbdRawUp:`).
27
+ - REST: `async_delete_screenshot`, `async_get_search_systems`, `async_browse_games`, `async_browse_menu` (rejects `..`), `async_get_remote_log`, and for Remote 0.5 `async_get_screen`, `async_get_playlog_status/summary/games/sessions`; `MisterStatus.core_session_time`/`game_session_time`, `disks`.
28
+ - `MDNS_SERVICE_TYPE` for zeroconf discovery.
29
+
30
+ ## 0.1.2
31
+
32
+ - Add online documentation (MkDocs Material) at <https://hudsonbrendon.github.io/python-mister-fpga/> and enrich public-API docstrings.
33
+
34
+ ## 0.1.1
35
+
36
+ - Export `DEFAULT_SSH_PORT`, `DEFAULT_SSH_USERNAME` and `SSH_PROBE_CMD` from the top-level package so consumers can reuse them without duplicating.
37
+
38
+ ## 0.1.0
39
+
40
+ - Initial release: async REST client (`MisterClient`/`MisterStatus`/`MisterConnectionError`), WebSocket reducer + client (`apply_ws_message`/`MisterWebSocketClient`), and SSH telemetry (`parse_ssh_probe`/`MisterSSH`) for the MiSTer FPGA mrext Remote API.
@@ -90,3 +90,32 @@ async def main():
90
90
 
91
91
  asyncio.run(main())
92
92
  ```
93
+
94
+
95
+ ## Main health, volume and Super Attract Mode (SSH)
96
+
97
+ ```python
98
+ data = await ssh.async_probe()
99
+ data["cmd_fifo_pending"] # >0 on two probes in a row: Main is hung
100
+ data["volume"], data["muted"], data["game_id"], data["sam_game"]
101
+ await ssh.async_set_volume(5) # 0-7, never blocks on a hung Main
102
+ await ssh.async_send_command("load_core /media/fat/menu.rbf")
103
+ ```
104
+
105
+ ## Virtual gamepad (MiSTer Companion Remote daemon, port 9191)
106
+
107
+ ```python
108
+ from mister_fpga import MisterGamepad
109
+
110
+ pad = MisterGamepad("192.168.1.50")
111
+ await pad.async_press("start") # a real controller press: works in cores, menu, SAM
112
+ ```
113
+
114
+ ## RetroAchievements events (odelot Main with `debug=1`)
115
+
116
+ ```python
117
+ from mister_fpga import MisterRA
118
+
119
+ ra = MisterRA(ssh)
120
+ offset, events = await ra.async_read_log(0) # login / game_identified / achievement / ...
121
+ ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-mister-fpga"
7
- version = "0.2.0"
7
+ version = "0.3.1"
8
8
  description = "Async Python client for the MiSTer FPGA mrext Remote API (REST + WebSocket) and SSH telemetry"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1,97 @@
1
+ """Async Python client for the MiSTer FPGA mrext Remote API."""
2
+ from .client import (
3
+ MisterClient,
4
+ MisterConnectionError,
5
+ MisterStatus,
6
+ MisterUnsupportedError,
7
+ )
8
+ from .const import (
9
+ BACKGROUND_MODES,
10
+ CMD_FIFO,
11
+ DEFAULT_PORT,
12
+ DEFAULT_SSH_PORT,
13
+ DEFAULT_SSH_USERNAME,
14
+ INI_VIDEO_KEYS,
15
+ KEYBOARD_NAMES,
16
+ MDNS_SERVICE_TYPE,
17
+ RA_STATUS_CMD,
18
+ RA_SUPPORTED_SYSTEMS,
19
+ RA_USERNAME_PLACEHOLDER,
20
+ SSH_PROBE_CMD,
21
+ WS_PATH,
22
+ MisterRAStatus,
23
+ MisterRAWebStats,
24
+ RAAchievement,
25
+ RAGameProgress,
26
+ )
27
+ from .gamepad import (
28
+ DEFAULT_GAMEPAD_PATH,
29
+ DEFAULT_GAMEPAD_PORT,
30
+ GAMEPAD_ACTIONS,
31
+ GAMEPAD_BUTTONS,
32
+ MisterGamepad,
33
+ MisterGamepadError,
34
+ )
35
+ from .ra import MisterRA, MisterRAError, parse_ra_log, parse_ra_status
36
+ from .ra_web import MisterRAWeb, MisterRAWebError
37
+ from .ssh import (
38
+ MisterMainUnresponsiveError,
39
+ MisterSSH,
40
+ parse_game_id,
41
+ parse_ssh_probe,
42
+ parse_volume,
43
+ )
44
+ from .websocket import (
45
+ MisterIndexStatus,
46
+ MisterWebSocketClient,
47
+ apply_ws_message,
48
+ parse_index_status,
49
+ split_menu_navigation,
50
+ )
51
+
52
+ __all__ = [
53
+ "BACKGROUND_MODES",
54
+ "CMD_FIFO",
55
+ "DEFAULT_GAMEPAD_PATH",
56
+ "DEFAULT_GAMEPAD_PORT",
57
+ "GAMEPAD_ACTIONS",
58
+ "GAMEPAD_BUTTONS",
59
+ "MDNS_SERVICE_TYPE",
60
+ "MisterGamepad",
61
+ "MisterGamepadError",
62
+ "MisterIndexStatus",
63
+ "MisterMainUnresponsiveError",
64
+ "MisterUnsupportedError",
65
+ "parse_game_id",
66
+ "parse_index_status",
67
+ "parse_ra_log",
68
+ "parse_volume",
69
+ "split_menu_navigation",
70
+ "MisterClient",
71
+ "MisterStatus",
72
+ "MisterConnectionError",
73
+ "MisterRA",
74
+ "MisterRAError",
75
+ "MisterRAStatus",
76
+ "MisterRAWeb",
77
+ "MisterRAWebError",
78
+ "MisterRAWebStats",
79
+ "MisterWebSocketClient",
80
+ "apply_ws_message",
81
+ "MisterSSH",
82
+ "parse_ra_status",
83
+ "parse_ssh_probe",
84
+ "RAAchievement",
85
+ "RAGameProgress",
86
+ "KEYBOARD_NAMES",
87
+ "INI_VIDEO_KEYS",
88
+ "RA_STATUS_CMD",
89
+ "RA_SUPPORTED_SYSTEMS",
90
+ "RA_USERNAME_PLACEHOLDER",
91
+ "WS_PATH",
92
+ "DEFAULT_PORT",
93
+ "DEFAULT_SSH_PORT",
94
+ "DEFAULT_SSH_USERNAME",
95
+ "SSH_PROBE_CMD",
96
+ ]
97
+ __version__ = "0.3.1"