python-mister-fpga 0.3.0__tar.gz → 0.3.2__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 (35) hide show
  1. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/CHANGELOG.md +6 -0
  2. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/PKG-INFO +1 -1
  3. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/docs/changelog.md +6 -0
  4. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/pyproject.toml +1 -1
  5. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/__init__.py +1 -1
  6. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/ssh.py +10 -10
  7. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/websocket.py +6 -2
  8. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/tests/test_ra.py +1 -1
  9. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/tests/test_ra_web.py +1 -1
  10. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/tests/test_websocket.py +9 -0
  11. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/.github/workflows/docs.yml +0 -0
  12. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/.github/workflows/release.yml +0 -0
  13. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/.github/workflows/tests.yml +0 -0
  14. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/.gitignore +0 -0
  15. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/LICENSE +0 -0
  16. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/README.md +0 -0
  17. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/assets/dark_logo.png +0 -0
  18. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/assets/icon.png +0 -0
  19. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/assets/logo.png +0 -0
  20. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/docs/api.md +0 -0
  21. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/docs/assets/dark_logo.png +0 -0
  22. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/docs/assets/icon.png +0 -0
  23. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/docs/assets/logo.png +0 -0
  24. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/docs/index.md +0 -0
  25. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/docs/usage.md +0 -0
  26. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/mkdocs.yml +0 -0
  27. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/client.py +0 -0
  28. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/const.py +0 -0
  29. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/gamepad.py +0 -0
  30. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/py.typed +0 -0
  31. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/ra.py +0 -0
  32. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/src/mister_fpga/ra_web.py +0 -0
  33. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/tests/test_client.py +0 -0
  34. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/tests/test_gamepad_ralog.py +0 -0
  35. {python_mister_fpga-0.3.0 → python_mister_fpga-0.3.2}/tests/test_ssh.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2
4
+ - WebSocket reducer: a launch from the menu emits `gameRunning` before `coreRunning`; the core update no longer wipes that game. Only a switch from another core clears the stale game/system.
5
+
6
+ ## 0.3.1
7
+ - 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.
8
+
3
9
  ## 0.3.0
4
10
 
5
11
  Findings from reverse-engineering mrext Remote 0.4/0.5 and odelot's Main_MiSTer on a real device.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-mister-fpga
3
- Version: 0.3.0
3
+ Version: 0.3.2
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/
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2
4
+ - WebSocket reducer: a launch from the menu emits `gameRunning` before `coreRunning`; the core update no longer wipes that game. Only a switch from another core clears the stale game/system.
5
+
6
+ ## 0.3.1
7
+ - 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.
8
+
3
9
  ## 0.3.0
4
10
 
5
11
  Findings from reverse-engineering mrext Remote 0.4/0.5 and odelot's Main_MiSTer on a real device.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-mister-fpga"
7
- version = "0.3.0"
7
+ version = "0.3.2"
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"
@@ -94,4 +94,4 @@ __all__ = [
94
94
  "DEFAULT_SSH_USERNAME",
95
95
  "SSH_PROBE_CMD",
96
96
  ]
97
- __version__ = "0.3.0"
97
+ __version__ = "0.3.2"
@@ -5,6 +5,8 @@ import asyncio
5
5
  import logging
6
6
  import shlex
7
7
 
8
+ import asyncssh
9
+
8
10
  from .const import CMD_FIFO, SSH_PROBE_CMD
9
11
 
10
12
  _LOGGER = logging.getLogger(__name__)
@@ -41,17 +43,17 @@ def parse_ssh_probe(raw: str) -> dict:
41
43
  * ``cpu_load_1m`` (``float | None``) — 1-minute load average.
42
44
  * ``memory_used_percent`` (``float | None``) — RAM usage 0–100.
43
45
  * ``firmware_timestamp`` (``int | None``) — mtime of the MiSTer binary.
44
- * ``cmd_fifo_pending`` (``int | None``) — bytes Main has not read from
45
- ``/dev/MiSTer_cmd``. Non-zero on two consecutive probes means Main
46
- is hung (e.g. a core stopped answering the SPI handshake).
47
- * ``main_open_fds`` (``int | None``) — open descriptors of the Main
48
- process. Some builds leak one per core load; 1024 is the limit.
46
+ * ``cmd_fifo_pending`` (``int | None``) — unread command-FIFO bytes.
47
+ * ``main_open_fds`` (``int | None``) — open descriptors of Main.
49
48
  * ``volume`` (``int | None``) — 0-7, 7 loudest.
50
49
  * ``muted`` (``bool | None``).
51
- * ``game_id`` (``str | None``) — CRC32 or disc serial of the loaded game.
50
+ * ``game_id`` (``str | None``) — CRC32 or disc serial of the game.
52
51
  * ``sam_active`` (``bool | None``) — Super Attract Mode is cycling.
53
- * ``sam_game`` (``str | None``) — game SAM is showing, e.g.
54
- ``"Breakers (neogeo)"``.
52
+ * ``sam_game`` (``str | None``) — game SAM shows, e.g. ``Breakers (neogeo)``.
53
+
54
+ ``cmd_fifo_pending`` above zero on two probes in a row means Main is
55
+ hung (its loop also drains ``/dev/MiSTer_cmd``). Some Main builds
56
+ leak one descriptor per core load; the limit is 1024.
55
57
  """
56
58
  parts = [p.strip() for p in raw.split(_SEP)]
57
59
  while len(parts) < 11:
@@ -148,8 +150,6 @@ class MisterSSH:
148
150
  async def _ensure(self) -> None:
149
151
  if self._conn is not None:
150
152
  return
151
- import asyncssh
152
-
153
153
  self._conn = await asyncssh.connect(
154
154
  self.host,
155
155
  port=self.port,
@@ -72,8 +72,9 @@ def apply_ws_message(
72
72
 
73
73
  Understood prefixes (``prefix:rest``):
74
74
 
75
- * ``coreRunning`` — updates :attr:`~mister_fpga.MisterStatus.core`; a new
76
- core clears the (now stale) system and game fields.
75
+ * ``coreRunning`` — updates :attr:`~mister_fpga.MisterStatus.core`;
76
+ switching from another core clears the stale system and game fields
77
+ (a launch from the menu sends ``gameRunning`` first, so that is kept).
77
78
  * ``gameRunning`` — updates :attr:`~mister_fpga.MisterStatus.game` and
78
79
  :attr:`~mister_fpga.MisterStatus.game_name` (``<system>/<file>``, or the
79
80
  bare core name for arcade).
@@ -104,6 +105,9 @@ def apply_ws_message(
104
105
  )
105
106
  if core == status.core:
106
107
  return status, menu_path, index_state
108
+ if status.core is None:
109
+ # On launch mrext sends gameRunning before coreRunning; keep the game.
110
+ return replace(status, core=core), menu_path, index_state
107
111
  return (
108
112
  replace(
109
113
  status, core=core, system=None, system_name=None,
@@ -160,4 +160,4 @@ def test_public_exports():
160
160
  assert hasattr(mister_fpga, "MisterRAStatus")
161
161
  assert hasattr(mister_fpga, "MisterRAError")
162
162
  assert hasattr(mister_fpga, "RA_SUPPORTED_SYSTEMS")
163
- assert mister_fpga.__version__ == "0.3.0"
163
+ assert mister_fpga.__version__ == "0.3.2"
@@ -254,7 +254,7 @@ async def test_validate_success_and_failure():
254
254
 
255
255
 
256
256
  def test_top_level_exports():
257
- assert mister_fpga.__version__ == "0.3.0"
257
+ assert mister_fpga.__version__ == "0.3.2"
258
258
  assert hasattr(mister_fpga, "MisterRAWeb")
259
259
  assert hasattr(mister_fpga, "MisterRAWebError")
260
260
  assert hasattr(mister_fpga, "MisterRAWebStats")
@@ -111,3 +111,12 @@ def test_parse_index_status_progress():
111
111
  assert (idx.total_steps, idx.current_step) == (10, 3)
112
112
  assert idx.description == "Indexing SNES, part 2"
113
113
  assert parse_index_status("y,n,0,0,").indexing is False
114
+
115
+
116
+ def test_core_after_game_on_launch_keeps_game():
117
+ # mrext order on launch: coreRunning: -> gameRunning:SNES/x -> coreRunning:SNES
118
+ status = MisterStatus(online=True, core="sf2")
119
+ for frame in ("coreRunning:", "gameRunning:SNES/x.sfc", "coreRunning:SNES"):
120
+ status, _, _ = apply_ws_message(frame, status, None, (False, False))
121
+ assert status.core == "SNES"
122
+ assert status.game_name == "x"