picokvm-client 0.1.0__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.
- picokvm_client-0.1.0/.gitignore +14 -0
- picokvm_client-0.1.0/CHANGELOG.md +24 -0
- picokvm_client-0.1.0/LICENSE +21 -0
- picokvm_client-0.1.0/PKG-INFO +177 -0
- picokvm_client-0.1.0/README.md +141 -0
- picokvm_client-0.1.0/examples/01_login_and_status.py +49 -0
- picokvm_client-0.1.0/examples/02_send_text_and_combo.py +41 -0
- picokvm_client-0.1.0/examples/03_mount_iso_and_reboot.py +49 -0
- picokvm_client-0.1.0/pyproject.toml +85 -0
- picokvm_client-0.1.0/src/picokvm_client/__init__.py +38 -0
- picokvm_client-0.1.0/src/picokvm_client/_cli.py +386 -0
- picokvm_client-0.1.0/src/picokvm_client/_hid.py +259 -0
- picokvm_client-0.1.0/src/picokvm_client/client.py +500 -0
- picokvm_client-0.1.0/src/picokvm_client/exceptions.py +131 -0
- picokvm_client-0.1.0/src/picokvm_client/methods.py +88 -0
- picokvm_client-0.1.0/src/picokvm_client/py.typed +0 -0
- picokvm_client-0.1.0/tests/__init__.py +0 -0
- picokvm_client-0.1.0/tests/test_client.py +896 -0
- picokvm_client-0.1.0/tests/test_picokvm_cli.py +428 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `picokvm-client` are documented in this file.
|
|
4
|
+
|
|
5
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-06-23
|
|
9
|
+
|
|
10
|
+
Initial release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `PicoKVMClient`: synchronous JSON-RPC 2.0 client for the Luckfox
|
|
15
|
+
PicoKVM HTTP API, built on `httpx` (transport and cookie auth) and
|
|
16
|
+
`jsonrpcclient` (request framing). Typed wrappers cover login, HID
|
|
17
|
+
input (type, click, key, combo), video state, virtual-media mount,
|
|
18
|
+
power and reset, and device introspection.
|
|
19
|
+
- `rpc(method, **params)`: generic JSON-RPC call for any other device
|
|
20
|
+
method.
|
|
21
|
+
- `PicoKVMError` hierarchy (`AuthError`, `TransportError`, `RpcError`)
|
|
22
|
+
rooted at the stdlib `Exception`, each with advisory `exit_code`,
|
|
23
|
+
`hint`, and `retryable` attributes.
|
|
24
|
+
- `picokvm` command-line tool.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Onur Celep
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: picokvm-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client for the Luckfox PicoKVM JSON-RPC API
|
|
5
|
+
Project-URL: Homepage, https://github.com/onurcelep/picokvm-client
|
|
6
|
+
Project-URL: Repository, https://github.com/onurcelep/picokvm-client
|
|
7
|
+
Project-URL: Issues, https://github.com/onurcelep/picokvm-client/issues
|
|
8
|
+
Author-email: Onur Celep <onurcelep@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: automation,hid,jetkvm,jsonrpc,kvm,picokvm
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Classifier: Topic :: System :: Hardware
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: click<9,>=8.1
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: jsonrpcclient>=4.0.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Requires-Dist: typer>=0.9.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: gitlint>=0.19.1; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# picokvm-client
|
|
38
|
+
|
|
39
|
+
Python client for the [Luckfox PicoKVM](https://github.com/LuckfoxTECH/kvm).
|
|
40
|
+
A thin, synchronous wrapper around its JSON-RPC 2.0 API (`POST /api/rpc`)
|
|
41
|
+
and cookie-auth endpoint (`POST /auth/login-local`), built on `httpx` and
|
|
42
|
+
`jsonrpcclient`.
|
|
43
|
+
|
|
44
|
+
> **Unofficial.** Independent, community-built client. Not affiliated with,
|
|
45
|
+
> authorized by, or endorsed by Luckfox or JetKVM. "PicoKVM" and "JetKVM"
|
|
46
|
+
> belong to their respective owners and are used here only to describe
|
|
47
|
+
> compatibility. No vendor source code is included; this reimplements the
|
|
48
|
+
> device's public JSON-RPC protocol and standard USB HID Boot-Protocol
|
|
49
|
+
> reports.
|
|
50
|
+
|
|
51
|
+
## Compatibility
|
|
52
|
+
|
|
53
|
+
Targets the Luckfox PicoKVM. Its firmware is a Luckfox-Pico fork of
|
|
54
|
+
[JetKVM](https://github.com/jetkvm/kvm), so the wire protocol currently
|
|
55
|
+
overlaps with JetKVM and its other forks. This client is only tested against
|
|
56
|
+
the PicoKVM and makes no compatibility promise to JetKVM; a JetKVM is better
|
|
57
|
+
served by a dedicated `jetkvm-client`.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install picokvm-client
|
|
63
|
+
# or
|
|
64
|
+
uv add picokvm-client
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## CLI
|
|
68
|
+
|
|
69
|
+
The package installs a `picokvm` command:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export PICOKVM_URL=http://kvm.example PICOKVM_PASSWORD=hunter2
|
|
73
|
+
|
|
74
|
+
picokvm ping
|
|
75
|
+
picokvm device-id
|
|
76
|
+
picokvm video-state
|
|
77
|
+
picokvm type "hello world"
|
|
78
|
+
picokvm combo "Ctrl+Alt+Del"
|
|
79
|
+
picokvm rpc getJigglerState
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`--url` and `--password` override the environment variables. Run
|
|
83
|
+
`picokvm --help` for the full command list.
|
|
84
|
+
|
|
85
|
+
> **Safety.** The HID commands (`type`, `key`, `combo`, `click`) inject input
|
|
86
|
+
> into whatever host is attached to the KVM. A mistake can lock the keyboard,
|
|
87
|
+
> type into the wrong window, or click at random coordinates. Test against a
|
|
88
|
+
> throwaway target before using these in automation.
|
|
89
|
+
|
|
90
|
+
## Library
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from picokvm_client import PicoKVMClient
|
|
94
|
+
|
|
95
|
+
with PicoKVMClient("http://kvm.example", password="hunter2") as kvm:
|
|
96
|
+
if not kvm.ping():
|
|
97
|
+
raise RuntimeError("KVM not responding")
|
|
98
|
+
|
|
99
|
+
state = kvm.get_video_state()
|
|
100
|
+
print(f"Video: {state.width}x{state.height}, ready={state.ready}")
|
|
101
|
+
|
|
102
|
+
kvm.key_combo("Ctrl+Alt+Del")
|
|
103
|
+
kvm.type_text("hello world\n")
|
|
104
|
+
kvm.click(state.width // 2, state.height // 2)
|
|
105
|
+
|
|
106
|
+
kvm.mount_with_http("https://files.example.com/installer.iso")
|
|
107
|
+
kvm.trigger_reset()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The client owns an `httpx.Client` that holds the session cookie. Use it as a
|
|
111
|
+
context manager so the connection pool and cookie are released on exit.
|
|
112
|
+
|
|
113
|
+
## Methods
|
|
114
|
+
|
|
115
|
+
Common operations have typed methods. Anything else on the device is reachable
|
|
116
|
+
through `client.rpc(method, **params)`, a generic JSON-RPC call.
|
|
117
|
+
|
|
118
|
+
| Family | Typed methods | Via `.rpc()` |
|
|
119
|
+
|-------------------|--------------------------------------------------------------------------|--------------------|
|
|
120
|
+
| HID input | `keyboard_report`, `abs_mouse_report`, `rel_mouse_report`, `wheel_report`, `get_keyboard_led_state`, `send_usb_wakeup_signal`, `type_text`, `click`, `key_press`, `key_combo` | macros, layout get/set, jiggler |
|
|
121
|
+
| Video | `get_video_state`, `wait_for_video` | EDID, capture |
|
|
122
|
+
| Virtual media | `mount_with_http`, `mount_built_in_image`, `mount_with_storage`, `unmount_image` | upload, listing |
|
|
123
|
+
| Device management | `get_device_id`, `ping`, `reboot`, `send_wol_magic_packet` | network, OTA, logs |
|
|
124
|
+
| Power | `trigger_power`, `trigger_reset` | hold-power |
|
|
125
|
+
| Auth | `login` (called automatically by `__enter__` when a password is set) | logout |
|
|
126
|
+
|
|
127
|
+
## Exceptions
|
|
128
|
+
|
|
129
|
+
All exceptions inherit from `PicoKVMError` (itself a plain `Exception`, no
|
|
130
|
+
third-party base). Each carries advisory `exit_code`, `hint`, and `retryable`
|
|
131
|
+
attributes that a caller can map onto its own error type.
|
|
132
|
+
|
|
133
|
+
| Exception | Raised on |
|
|
134
|
+
|------------------|----------------------------------------------------------|
|
|
135
|
+
| `AuthError` | HTTP 401 from login or RPC |
|
|
136
|
+
| `TransportError` | non-2xx HTTP (other than 401), invalid JSON, network failure |
|
|
137
|
+
| `RpcError` | a JSON-RPC 2.0 `error` response |
|
|
138
|
+
| `PicoKVMError` | base class for the above |
|
|
139
|
+
|
|
140
|
+
## Examples
|
|
141
|
+
|
|
142
|
+
Runnable scripts in [examples/](examples/) (each reads `PICOKVM_URL` and
|
|
143
|
+
`PICOKVM_PASSWORD`):
|
|
144
|
+
|
|
145
|
+
| Script | What it does |
|
|
146
|
+
|------------------------------------------------------------------|------------------------------------------------|
|
|
147
|
+
| [01_login_and_status.py](examples/01_login_and_status.py) | Connect, ping, print device ID and video state |
|
|
148
|
+
| [02_send_text_and_combo.py](examples/02_send_text_and_combo.py) | Type a string and send a key combo |
|
|
149
|
+
| [03_mount_iso_and_reboot.py](examples/03_mount_iso_and_reboot.py)| Mount an ISO over HTTP and wait for boot |
|
|
150
|
+
|
|
151
|
+
## Testing
|
|
152
|
+
|
|
153
|
+
Tests drive the client through `httpx.MockTransport`, so no sockets are
|
|
154
|
+
opened. Each typed method asserts the exact JSON-RPC `method` and `params` it
|
|
155
|
+
puts on the wire.
|
|
156
|
+
|
|
157
|
+
The HID commands (`type_text`, `click`, `key_press`, `key_combo`) are checked
|
|
158
|
+
at the payload level only, not against real hardware. Run a manual smoke test
|
|
159
|
+
on a throwaway target before relying on them.
|
|
160
|
+
|
|
161
|
+
## Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
uv sync --extra dev
|
|
165
|
+
pre-commit install --install-hooks
|
|
166
|
+
pre-commit install --hook-type commit-msg
|
|
167
|
+
git config commit.template .gitmessage
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Checks: `uv run pytest`, `uv run ruff check src tests`,
|
|
171
|
+
`uv run mypy src/picokvm_client --strict`. Commit messages follow `.gitlint`
|
|
172
|
+
(subject 10 to 72 chars, body wrapped at 72, `Signed-off-by` required, so use
|
|
173
|
+
`git commit -s`).
|
|
174
|
+
|
|
175
|
+
## Issues
|
|
176
|
+
|
|
177
|
+
File issues at <https://github.com/onurcelep/picokvm-client/issues>.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# picokvm-client
|
|
2
|
+
|
|
3
|
+
Python client for the [Luckfox PicoKVM](https://github.com/LuckfoxTECH/kvm).
|
|
4
|
+
A thin, synchronous wrapper around its JSON-RPC 2.0 API (`POST /api/rpc`)
|
|
5
|
+
and cookie-auth endpoint (`POST /auth/login-local`), built on `httpx` and
|
|
6
|
+
`jsonrpcclient`.
|
|
7
|
+
|
|
8
|
+
> **Unofficial.** Independent, community-built client. Not affiliated with,
|
|
9
|
+
> authorized by, or endorsed by Luckfox or JetKVM. "PicoKVM" and "JetKVM"
|
|
10
|
+
> belong to their respective owners and are used here only to describe
|
|
11
|
+
> compatibility. No vendor source code is included; this reimplements the
|
|
12
|
+
> device's public JSON-RPC protocol and standard USB HID Boot-Protocol
|
|
13
|
+
> reports.
|
|
14
|
+
|
|
15
|
+
## Compatibility
|
|
16
|
+
|
|
17
|
+
Targets the Luckfox PicoKVM. Its firmware is a Luckfox-Pico fork of
|
|
18
|
+
[JetKVM](https://github.com/jetkvm/kvm), so the wire protocol currently
|
|
19
|
+
overlaps with JetKVM and its other forks. This client is only tested against
|
|
20
|
+
the PicoKVM and makes no compatibility promise to JetKVM; a JetKVM is better
|
|
21
|
+
served by a dedicated `jetkvm-client`.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install picokvm-client
|
|
27
|
+
# or
|
|
28
|
+
uv add picokvm-client
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## CLI
|
|
32
|
+
|
|
33
|
+
The package installs a `picokvm` command:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export PICOKVM_URL=http://kvm.example PICOKVM_PASSWORD=hunter2
|
|
37
|
+
|
|
38
|
+
picokvm ping
|
|
39
|
+
picokvm device-id
|
|
40
|
+
picokvm video-state
|
|
41
|
+
picokvm type "hello world"
|
|
42
|
+
picokvm combo "Ctrl+Alt+Del"
|
|
43
|
+
picokvm rpc getJigglerState
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`--url` and `--password` override the environment variables. Run
|
|
47
|
+
`picokvm --help` for the full command list.
|
|
48
|
+
|
|
49
|
+
> **Safety.** The HID commands (`type`, `key`, `combo`, `click`) inject input
|
|
50
|
+
> into whatever host is attached to the KVM. A mistake can lock the keyboard,
|
|
51
|
+
> type into the wrong window, or click at random coordinates. Test against a
|
|
52
|
+
> throwaway target before using these in automation.
|
|
53
|
+
|
|
54
|
+
## Library
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from picokvm_client import PicoKVMClient
|
|
58
|
+
|
|
59
|
+
with PicoKVMClient("http://kvm.example", password="hunter2") as kvm:
|
|
60
|
+
if not kvm.ping():
|
|
61
|
+
raise RuntimeError("KVM not responding")
|
|
62
|
+
|
|
63
|
+
state = kvm.get_video_state()
|
|
64
|
+
print(f"Video: {state.width}x{state.height}, ready={state.ready}")
|
|
65
|
+
|
|
66
|
+
kvm.key_combo("Ctrl+Alt+Del")
|
|
67
|
+
kvm.type_text("hello world\n")
|
|
68
|
+
kvm.click(state.width // 2, state.height // 2)
|
|
69
|
+
|
|
70
|
+
kvm.mount_with_http("https://files.example.com/installer.iso")
|
|
71
|
+
kvm.trigger_reset()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The client owns an `httpx.Client` that holds the session cookie. Use it as a
|
|
75
|
+
context manager so the connection pool and cookie are released on exit.
|
|
76
|
+
|
|
77
|
+
## Methods
|
|
78
|
+
|
|
79
|
+
Common operations have typed methods. Anything else on the device is reachable
|
|
80
|
+
through `client.rpc(method, **params)`, a generic JSON-RPC call.
|
|
81
|
+
|
|
82
|
+
| Family | Typed methods | Via `.rpc()` |
|
|
83
|
+
|-------------------|--------------------------------------------------------------------------|--------------------|
|
|
84
|
+
| HID input | `keyboard_report`, `abs_mouse_report`, `rel_mouse_report`, `wheel_report`, `get_keyboard_led_state`, `send_usb_wakeup_signal`, `type_text`, `click`, `key_press`, `key_combo` | macros, layout get/set, jiggler |
|
|
85
|
+
| Video | `get_video_state`, `wait_for_video` | EDID, capture |
|
|
86
|
+
| Virtual media | `mount_with_http`, `mount_built_in_image`, `mount_with_storage`, `unmount_image` | upload, listing |
|
|
87
|
+
| Device management | `get_device_id`, `ping`, `reboot`, `send_wol_magic_packet` | network, OTA, logs |
|
|
88
|
+
| Power | `trigger_power`, `trigger_reset` | hold-power |
|
|
89
|
+
| Auth | `login` (called automatically by `__enter__` when a password is set) | logout |
|
|
90
|
+
|
|
91
|
+
## Exceptions
|
|
92
|
+
|
|
93
|
+
All exceptions inherit from `PicoKVMError` (itself a plain `Exception`, no
|
|
94
|
+
third-party base). Each carries advisory `exit_code`, `hint`, and `retryable`
|
|
95
|
+
attributes that a caller can map onto its own error type.
|
|
96
|
+
|
|
97
|
+
| Exception | Raised on |
|
|
98
|
+
|------------------|----------------------------------------------------------|
|
|
99
|
+
| `AuthError` | HTTP 401 from login or RPC |
|
|
100
|
+
| `TransportError` | non-2xx HTTP (other than 401), invalid JSON, network failure |
|
|
101
|
+
| `RpcError` | a JSON-RPC 2.0 `error` response |
|
|
102
|
+
| `PicoKVMError` | base class for the above |
|
|
103
|
+
|
|
104
|
+
## Examples
|
|
105
|
+
|
|
106
|
+
Runnable scripts in [examples/](examples/) (each reads `PICOKVM_URL` and
|
|
107
|
+
`PICOKVM_PASSWORD`):
|
|
108
|
+
|
|
109
|
+
| Script | What it does |
|
|
110
|
+
|------------------------------------------------------------------|------------------------------------------------|
|
|
111
|
+
| [01_login_and_status.py](examples/01_login_and_status.py) | Connect, ping, print device ID and video state |
|
|
112
|
+
| [02_send_text_and_combo.py](examples/02_send_text_and_combo.py) | Type a string and send a key combo |
|
|
113
|
+
| [03_mount_iso_and_reboot.py](examples/03_mount_iso_and_reboot.py)| Mount an ISO over HTTP and wait for boot |
|
|
114
|
+
|
|
115
|
+
## Testing
|
|
116
|
+
|
|
117
|
+
Tests drive the client through `httpx.MockTransport`, so no sockets are
|
|
118
|
+
opened. Each typed method asserts the exact JSON-RPC `method` and `params` it
|
|
119
|
+
puts on the wire.
|
|
120
|
+
|
|
121
|
+
The HID commands (`type_text`, `click`, `key_press`, `key_combo`) are checked
|
|
122
|
+
at the payload level only, not against real hardware. Run a manual smoke test
|
|
123
|
+
on a throwaway target before relying on them.
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv sync --extra dev
|
|
129
|
+
pre-commit install --install-hooks
|
|
130
|
+
pre-commit install --hook-type commit-msg
|
|
131
|
+
git config commit.template .gitmessage
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Checks: `uv run pytest`, `uv run ruff check src tests`,
|
|
135
|
+
`uv run mypy src/picokvm_client --strict`. Commit messages follow `.gitlint`
|
|
136
|
+
(subject 10 to 72 chars, body wrapped at 72, `Signed-off-by` required, so use
|
|
137
|
+
`git commit -s`).
|
|
138
|
+
|
|
139
|
+
## Issues
|
|
140
|
+
|
|
141
|
+
File issues at <https://github.com/onurcelep/picokvm-client/issues>.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Example 01: connect, ping, print device ID and video state.
|
|
2
|
+
|
|
3
|
+
Prerequisites:
|
|
4
|
+
export PICOKVM_URL=http://kvm.example
|
|
5
|
+
export PICOKVM_PASSWORD=hunter2 # omit if device is in noPassword mode
|
|
6
|
+
|
|
7
|
+
What this script does:
|
|
8
|
+
Opens a session, verifies the firmware is responding, then prints
|
|
9
|
+
the device ID and HDMI input state. No host-side side effects --
|
|
10
|
+
safe to run against any KVM you have access to.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from picokvm_client import PicoKVMClient, PicoKVMError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> int:
|
|
22
|
+
url = os.environ.get("PICOKVM_URL")
|
|
23
|
+
if not url:
|
|
24
|
+
sys.stderr.write("error: set PICOKVM_URL\n")
|
|
25
|
+
return 2
|
|
26
|
+
password = os.environ.get("PICOKVM_PASSWORD")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
with PicoKVMClient(url, password=password) as kvm:
|
|
30
|
+
if not kvm.ping():
|
|
31
|
+
sys.stderr.write("KVM did not respond to ping\n")
|
|
32
|
+
return 1
|
|
33
|
+
|
|
34
|
+
print(f"device id: {kvm.get_device_id()}")
|
|
35
|
+
|
|
36
|
+
state = kvm.get_video_state()
|
|
37
|
+
print(
|
|
38
|
+
f"video : {state.width}x{state.height} @ {state.fps:g}fps "
|
|
39
|
+
f"ready={state.ready} error={state.error!r}"
|
|
40
|
+
)
|
|
41
|
+
except PicoKVMError as exc:
|
|
42
|
+
sys.stderr.write(f"error: {exc}\n")
|
|
43
|
+
return exc.exit_code
|
|
44
|
+
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Example 02: send keystrokes via the typed HID helpers.
|
|
2
|
+
|
|
3
|
+
Prerequisites:
|
|
4
|
+
export PICOKVM_URL=http://kvm.example
|
|
5
|
+
export PICOKVM_PASSWORD=hunter2 # omit if device is in noPassword mode
|
|
6
|
+
Host attached to the KVM should be at a focused text input.
|
|
7
|
+
|
|
8
|
+
What this script does:
|
|
9
|
+
Types a literal string and then issues a Ctrl+Alt+T combo.
|
|
10
|
+
BEWARE: this WILL inject characters into whatever has focus on the
|
|
11
|
+
host -- only run against a DUT you do not mind being typed into.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from picokvm_client import PicoKVMClient, PicoKVMError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> int:
|
|
23
|
+
url = os.environ.get("PICOKVM_URL")
|
|
24
|
+
if not url:
|
|
25
|
+
sys.stderr.write("error: set PICOKVM_URL\n")
|
|
26
|
+
return 2
|
|
27
|
+
password = os.environ.get("PICOKVM_PASSWORD")
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
with PicoKVMClient(url, password=password) as kvm:
|
|
31
|
+
kvm.type_text("hello world")
|
|
32
|
+
kvm.key_combo("Ctrl+Alt+T")
|
|
33
|
+
except PicoKVMError as exc:
|
|
34
|
+
sys.stderr.write(f"error: {exc}\n")
|
|
35
|
+
return exc.exit_code
|
|
36
|
+
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Example 03: mount an installer ISO over HTTP and trigger a reset.
|
|
2
|
+
|
|
3
|
+
Prerequisites:
|
|
4
|
+
export PICOKVM_URL=http://kvm.example
|
|
5
|
+
export PICOKVM_PASSWORD=hunter2 # omit if device is in noPassword mode
|
|
6
|
+
export PICOKVM_INSTALLER_URL=https://files.example.com/installer.iso
|
|
7
|
+
|
|
8
|
+
What this script does:
|
|
9
|
+
Mounts the ISO at PICOKVM_INSTALLER_URL as a virtual CD-ROM,
|
|
10
|
+
triggers a hard reset on the host so it boots from the new media,
|
|
11
|
+
then waits for the HDMI input to come back ready.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from picokvm_client import PicoKVMClient, PicoKVMError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> int:
|
|
23
|
+
url = os.environ.get("PICOKVM_URL")
|
|
24
|
+
iso = os.environ.get("PICOKVM_INSTALLER_URL")
|
|
25
|
+
if not url or not iso:
|
|
26
|
+
sys.stderr.write("error: set PICOKVM_URL and PICOKVM_INSTALLER_URL\n")
|
|
27
|
+
return 2
|
|
28
|
+
password = os.environ.get("PICOKVM_PASSWORD")
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
with PicoKVMClient(url, password=password) as kvm:
|
|
32
|
+
print(f"mounting {iso} ...")
|
|
33
|
+
kvm.mount_with_http(iso)
|
|
34
|
+
|
|
35
|
+
print("triggering reset ...")
|
|
36
|
+
kvm.trigger_reset()
|
|
37
|
+
|
|
38
|
+
print("waiting for video signal ...")
|
|
39
|
+
state = kvm.wait_for_video(timeout=120.0, poll_interval=2.0)
|
|
40
|
+
print(f"booted: {state.width}x{state.height} @ {state.fps:g}fps")
|
|
41
|
+
except PicoKVMError as exc:
|
|
42
|
+
sys.stderr.write(f"error: {exc}\n")
|
|
43
|
+
return exc.exit_code
|
|
44
|
+
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "picokvm-client"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Unofficial Python client for the Luckfox PicoKVM JSON-RPC API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Onur Celep", email = "onurcelep@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["picokvm", "kvm", "jsonrpc", "jetkvm", "automation", "hid"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Software Development :: Testing",
|
|
21
|
+
"Topic :: System :: Hardware",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
dependencies = [
|
|
25
|
+
"httpx>=0.27.0",
|
|
26
|
+
"jsonrpcclient>=4.0.0",
|
|
27
|
+
"pydantic>=2.0.0",
|
|
28
|
+
"typer>=0.9.0",
|
|
29
|
+
"click>=8.1,<9",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.4.0",
|
|
35
|
+
"pytest-mock>=3.12.0",
|
|
36
|
+
"respx>=0.21.0",
|
|
37
|
+
"ruff>=0.8.0",
|
|
38
|
+
"mypy>=1.0.0",
|
|
39
|
+
"pre-commit>=3.5.0",
|
|
40
|
+
"gitlint>=0.19.1",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
picokvm = "picokvm_client._cli:app"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/onurcelep/picokvm-client"
|
|
48
|
+
Repository = "https://github.com/onurcelep/picokvm-client"
|
|
49
|
+
Issues = "https://github.com/onurcelep/picokvm-client/issues"
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
requires = ["hatchling"]
|
|
53
|
+
build-backend = "hatchling.build"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/picokvm_client"]
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.sdist]
|
|
59
|
+
include = ["src/picokvm_client", "tests", "examples", "README.md", "CHANGELOG.md", "LICENSE"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 120
|
|
66
|
+
target-version = "py311"
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint]
|
|
69
|
+
select = [
|
|
70
|
+
"E", # pycodestyle errors
|
|
71
|
+
"W", # pycodestyle warnings
|
|
72
|
+
"F", # pyflakes
|
|
73
|
+
"I", # isort (import sorting)
|
|
74
|
+
"B", # flake8-bugbear
|
|
75
|
+
"C4", # flake8-comprehensions
|
|
76
|
+
"UP", # pyupgrade
|
|
77
|
+
]
|
|
78
|
+
ignore = ["E501"] # line length handled by the formatter
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.isort]
|
|
81
|
+
known-first-party = ["picokvm_client"]
|
|
82
|
+
|
|
83
|
+
[tool.mypy]
|
|
84
|
+
python_version = "3.11"
|
|
85
|
+
strict = true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Python client for the Luckfox PicoKVM device.
|
|
2
|
+
|
|
3
|
+
The PicoKVM exposes a JSON-RPC 2.0 API at ``POST /api/rpc`` (the same
|
|
4
|
+
methods the web UI drives) plus a session-cookie auth endpoint at
|
|
5
|
+
``POST /auth/login-local``. This package is a thin, synchronous client
|
|
6
|
+
on top of :mod:`httpx` and :mod:`jsonrpcclient` covering both.
|
|
7
|
+
|
|
8
|
+
The PicoKVM firmware is a Luckfox-Pico-based fork of `JetKVM
|
|
9
|
+
<https://github.com/jetkvm/kvm>`_; the wire protocol is largely shared
|
|
10
|
+
with upstream and other forks today, but **this client makes no
|
|
11
|
+
compatibility commitment** to JetKVM or its other forks. The PicoKVM
|
|
12
|
+
is the only device this library is tested against. If you have a
|
|
13
|
+
JetKVM, this library *might* work for the methods that haven't
|
|
14
|
+
diverged — but a separate ``jetkvm-client`` package would be the right
|
|
15
|
+
home for that contract.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from picokvm_client.client import PicoKVMClient
|
|
21
|
+
from picokvm_client.exceptions import (
|
|
22
|
+
AuthError,
|
|
23
|
+
PicoKVMError,
|
|
24
|
+
RpcError,
|
|
25
|
+
TransportError,
|
|
26
|
+
)
|
|
27
|
+
from picokvm_client.methods import KeyboardLedState, UsbState, VideoState
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"AuthError",
|
|
31
|
+
"KeyboardLedState",
|
|
32
|
+
"PicoKVMClient",
|
|
33
|
+
"PicoKVMError",
|
|
34
|
+
"RpcError",
|
|
35
|
+
"TransportError",
|
|
36
|
+
"UsbState",
|
|
37
|
+
"VideoState",
|
|
38
|
+
]
|