pyks2 1.0.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.
- pyks2-1.0.0/CHANGELOG.md +56 -0
- pyks2-1.0.0/LICENSE +21 -0
- pyks2-1.0.0/MANIFEST.in +5 -0
- pyks2-1.0.0/PKG-INFO +212 -0
- pyks2-1.0.0/README.md +179 -0
- pyks2-1.0.0/docs/METHODOLOGY.md +131 -0
- pyks2-1.0.0/docs/PROTOCOL.md +446 -0
- pyks2-1.0.0/docs/_config.yml +11 -0
- pyks2-1.0.0/docs/index.md +30 -0
- pyks2-1.0.0/examples/API_REFERENCE.json +317 -0
- pyks2-1.0.0/examples/README.md +44 -0
- pyks2-1.0.0/examples/apis.json +3 -0
- pyks2-1.0.0/examples/camera-shoot-finish-bulb.json +4 -0
- pyks2-1.0.0/examples/camera-shoot-response.json +7 -0
- pyks2-1.0.0/examples/camera-shoot-start-bulb.json +7 -0
- pyks2-1.0.0/examples/changes-events.jsonl +2 -0
- pyks2-1.0.0/examples/constants-camera.json +11 -0
- pyks2-1.0.0/examples/constants-device.json +7 -0
- pyks2-1.0.0/examples/constants-lens.json +2 -0
- pyks2-1.0.0/examples/constants-liveview.json +2 -0
- pyks2-1.0.0/examples/constants.json +145 -0
- pyks2-1.0.0/examples/dial-positions.json +262 -0
- pyks2-1.0.0/examples/error-400-bad-request.json +1 -0
- pyks2-1.0.0/examples/error-412-precondition.json +1 -0
- pyks2-1.0.0/examples/lens-focus-response.json +6 -0
- pyks2-1.0.0/examples/lens-identity-none.json +33 -0
- pyks2-1.0.0/examples/params-camera.json +13 -0
- pyks2-1.0.0/examples/params-device.json +5 -0
- pyks2-1.0.0/examples/params-lens.json +3 -0
- pyks2-1.0.0/examples/params-liveview.json +2 -0
- pyks2-1.0.0/examples/params.json +19 -0
- pyks2-1.0.0/examples/photos-latest-info.json +13 -0
- pyks2-1.0.0/examples/photos-listing.json +10 -0
- pyks2-1.0.0/examples/ping.json +4 -0
- pyks2-1.0.0/examples/props-camera.json +28 -0
- pyks2-1.0.0/examples/props-device.json +24 -0
- pyks2-1.0.0/examples/props-lens.json +5 -0
- pyks2-1.0.0/examples/props-liveview.json +3 -0
- pyks2-1.0.0/examples/props.json +54 -0
- pyks2-1.0.0/examples/status-camera.json +8 -0
- pyks2-1.0.0/examples/status-device-cardfull.json +19 -0
- pyks2-1.0.0/examples/status-device.json +16 -0
- pyks2-1.0.0/examples/status-lens.json +4 -0
- pyks2-1.0.0/examples/status-liveview.json +3 -0
- pyks2-1.0.0/examples/status.json +25 -0
- pyks2-1.0.0/examples/variables-camera.json +19 -0
- pyks2-1.0.0/examples/variables-device.json +19 -0
- pyks2-1.0.0/examples/variables-lens.json +5 -0
- pyks2-1.0.0/examples/variables-liveview.json +3 -0
- pyks2-1.0.0/examples/variables.json +40 -0
- pyks2-1.0.0/pyks2/__init__.py +69 -0
- pyks2-1.0.0/pyks2/cli.py +251 -0
- pyks2-1.0.0/pyks2/client.py +635 -0
- pyks2-1.0.0/pyks2/constants.py +115 -0
- pyks2-1.0.0/pyks2/errors.py +59 -0
- pyks2-1.0.0/pyks2/events.py +225 -0
- pyks2-1.0.0/pyks2/models.py +295 -0
- pyks2-1.0.0/pyks2.egg-info/PKG-INFO +212 -0
- pyks2-1.0.0/pyks2.egg-info/SOURCES.txt +67 -0
- pyks2-1.0.0/pyks2.egg-info/dependency_links.txt +1 -0
- pyks2-1.0.0/pyks2.egg-info/entry_points.txt +2 -0
- pyks2-1.0.0/pyks2.egg-info/requires.txt +6 -0
- pyks2-1.0.0/pyks2.egg-info/top_level.txt +1 -0
- pyks2-1.0.0/pyproject.toml +56 -0
- pyks2-1.0.0/setup.cfg +4 -0
- pyks2-1.0.0/tests/conftest.py +123 -0
- pyks2-1.0.0/tests/test_client.py +98 -0
- pyks2-1.0.0/tests/test_fixes.py +236 -0
- pyks2-1.0.0/tests/test_models.py +27 -0
pyks2-1.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **pyks2** are documented here. This project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [1.0.0] — 2026-07-16
|
|
7
|
+
|
|
8
|
+
First stable release: a complete, hardware-verified reverse-engineering of the
|
|
9
|
+
Pentax K-S2's built-in WiFi HTTP API, with a Python library, a CLI, and a full
|
|
10
|
+
protocol write-up. Every one of the camera's 38 API endpoint templates is
|
|
11
|
+
characterised — confirmed working, confirmed read-only, or confirmed
|
|
12
|
+
unsupported — with zero remaining unknowns.
|
|
13
|
+
|
|
14
|
+
### The dissection
|
|
15
|
+
- Complete map of the `/v1/*` API: five read groups
|
|
16
|
+
(`constants`/`params`/`variables`/`status`/`props`) × four subsystems
|
|
17
|
+
(`camera`/`lens`/`liveview`/`device`), plus capture, focus, photo, live view,
|
|
18
|
+
and the `/v1/changes` WebSocket.
|
|
19
|
+
- Two protocol laws documented: `errCode` lives in the body (not the HTTP
|
|
20
|
+
status), and datetime/numeric formats vary by endpoint.
|
|
21
|
+
- Full mode-dial characterisation (P/Sv/Tv/Av/TAv/M/**Bulb**/U1/U2/auto/scene/
|
|
22
|
+
HDR/movie), with a per-mode value-writability matrix driven by list emptiness.
|
|
23
|
+
- Hardware interlocks mapped and explained: AF/MF lever, mode dial, movie mode
|
|
24
|
+
disabling WiFi, the SD-door disconnect, device/lens read-only params, and the
|
|
25
|
+
WiFi AP's client isolation.
|
|
26
|
+
- 40 real captured responses in `examples/`, plus a machine-readable
|
|
27
|
+
`examples/API_REFERENCE.json`.
|
|
28
|
+
|
|
29
|
+
### Key findings that corrected earlier assumptions
|
|
30
|
+
- **`/v1/photos` does not hang.** The long-standing "it hangs indefinitely"
|
|
31
|
+
belief was a client-side artifact; the endpoint is reliable and scales gently
|
|
32
|
+
with file count. It also supports an undocumented `?limit=N`.
|
|
33
|
+
- **`shoot/start` / `shoot/finish` are Bulb exposure controls, not movie**, and
|
|
34
|
+
they work over WiFi when the dial is on B. (`bulb_start()`, `bulb_finish()`,
|
|
35
|
+
`bulb_exposure(seconds)`.)
|
|
36
|
+
- **`storages[].remain` is a frame count, not bytes.**
|
|
37
|
+
- **Lens identity is not exposed** over WiFi.
|
|
38
|
+
- **`/v1/liveview/zoom` is a no-op over WiFi** — accepts any param, returns 200,
|
|
39
|
+
but never changes the frame.
|
|
40
|
+
|
|
41
|
+
### Library
|
|
42
|
+
- `K_S2_WiFi` a camera-only HTTP client with typed models, defensive parsing
|
|
43
|
+
for the datetime/numeric quirks, and `errCode`-aware exceptions.
|
|
44
|
+
- Race-free capture via `capture()` (baseline → shoot → wait for the new file).
|
|
45
|
+
- Event-driven workflow via the `/v1/changes` WebSocket (`events()`), replacing
|
|
46
|
+
the official app's polling.
|
|
47
|
+
- Correct handling of dynamic capability lists (e.g. `avList`) and the
|
|
48
|
+
list-emptiness writability signal for `av`/`tv`.
|
|
49
|
+
|
|
50
|
+
### CLI
|
|
51
|
+
- `pyks2 ping | info | apis | lists | shoot | settings | focus | browse |
|
|
52
|
+
download | liveview | bulb | watch`.
|
|
53
|
+
|
|
54
|
+
### Not included (planned)
|
|
55
|
+
- A web GUI is planned for a later release; this version ships the library, CLI,
|
|
56
|
+
and documentation.
|
pyks2-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jamal El Siblany
|
|
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.
|
pyks2-1.0.0/MANIFEST.in
ADDED
pyks2-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyks2
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Python client and CLI for the Pentax K-S2's undocumented WiFi HTTP API, plus a reverse-engineering write-up. (Web GUI planned.)
|
|
5
|
+
Author-email: Jamal El Siblany <jamalsiblani@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PICKLERICK2005/pyks2
|
|
8
|
+
Project-URL: Documentation, https://PICKLERICK2005.github.io/pyks2/
|
|
9
|
+
Project-URL: Repository, https://github.com/PICKLERICK2005/pyks2
|
|
10
|
+
Project-URL: Issues, https://github.com/PICKLERICK2005/pyks2/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/PICKLERICK2005/pyks2/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: pentax,k-s2,camera,wifi,reverse-engineering,tethering,remote-control,mjpeg
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics :: Capture
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: requests>=2.25
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# pyks2
|
|
35
|
+
|
|
36
|
+
[](https://github.com/PICKLERICK2005/pyks2/actions/workflows/test.yml)
|
|
37
|
+
[](https://www.python.org/)
|
|
38
|
+
[](https://github.com/PICKLERICK2005/pyks2/blob/main/LICENSE)
|
|
39
|
+
|
|
40
|
+
A Python library and CLI for controlling the **Pentax K-S2** over its
|
|
41
|
+
built-in WiFi, built on a complete, hardware-verified reverse-engineering of
|
|
42
|
+
the camera's undocumented HTTP API. (A web GUI is planned; see below.)
|
|
43
|
+
|
|
44
|
+
The K-S2 has a WiFi remote-control API, but Pentax never documented it. This
|
|
45
|
+
project maps the **entire surface** of the K-S2's API against a physical camera,
|
|
46
|
+
writes it up as a proper dissection, and ships a clean client that is lighter
|
|
47
|
+
and more capable than the vendor's own Image Sync app.
|
|
48
|
+
|
|
49
|
+
Visit the pages website .
|
|
50
|
+
|
|
51
|
+
> **Two things make this more than "another camera library":**
|
|
52
|
+
> 1. A full [protocol dissection](https://picklerick2005.github.io/PyKS2/PROTOCOL.html) and
|
|
53
|
+
> [reverse-engineering methodology](https://picklerick2005.github.io/PyKS2/METHODOLOGY.html), every endpoint,
|
|
54
|
+
> every quirk, every hardware limitation, with the raw captures to prove it.
|
|
55
|
+
> 2. A design that **beats the official app**: event-driven via the camera's
|
|
56
|
+
> WebSocket instead of the poll-storm Image Sync uses (~90% of its total
|
|
57
|
+
> requests were just polling one endpoint while interacting with app actively).
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## What's here
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
pyks2/ the library (camera-only HTTP client, typed models, WS events)
|
|
65
|
+
├─ client.py K_S2_WiFi, the API client
|
|
66
|
+
├─ models.py typed response models (defensive parsing)
|
|
67
|
+
├─ events.py /v1/changes WebSocket client (stdlib, zero-dep)
|
|
68
|
+
├─ constants.py endpoints + capability enums
|
|
69
|
+
├─ errors.py typed exceptions (errCode-aware)
|
|
70
|
+
└─ cli.py the command-line interface
|
|
71
|
+
docs/ the reverse-engineering write-up (GitHub Pages source)
|
|
72
|
+
├─ PROTOCOL.md the complete API dissection
|
|
73
|
+
└─ METHODOLOGY.md how it was probed (approach, false trails, traffic capture)
|
|
74
|
+
examples/ real captured JSON responses + the machine-readable API reference
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install pyks2 # or: pip install -e . from a clone
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Join the camera's WiFi (`PENTAX_XXXXXX`), then:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from pyks2 import K_S2_WiFi
|
|
89
|
+
|
|
90
|
+
cam = K_S2_WiFi() # defaults to 192.168.0.1
|
|
91
|
+
assert cam.ping()
|
|
92
|
+
|
|
93
|
+
# take one photo safely: records the baseline, fires, waits for the NEW file,
|
|
94
|
+
# and downloads it all in one call (af="off" is MF-safe: always releases)
|
|
95
|
+
info = cam.capture(af="off", download_to="shot.dng")
|
|
96
|
+
print("captured:", info.path)
|
|
97
|
+
|
|
98
|
+
# change settings (validated by the camera; illegal values raise)
|
|
99
|
+
cam.set_camera_params(av="8.0", sv="400")
|
|
100
|
+
|
|
101
|
+
# browse the card
|
|
102
|
+
for photo in cam.list_photos():
|
|
103
|
+
print(photo.path)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Event-driven, no polling:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
with cam.events() as ev: # /v1/changes WebSocket
|
|
110
|
+
for change in ev:
|
|
111
|
+
if change.is_storage: # a frame just landed
|
|
112
|
+
print("captured:", cam.latest_info().path)
|
|
113
|
+
elif change.is_camera: # a setting changed on the body
|
|
114
|
+
print("settings:", cam.get_camera_params())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Live view (MJPEG):
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
for jpeg in cam.iter_liveview_frames(max_frames=1):
|
|
121
|
+
open("frame.jpg", "wb").write(jpeg)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## CLI
|
|
127
|
+
|
|
128
|
+
Everything the library does, from a terminal:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pyks2 ping
|
|
132
|
+
pyks2 info # model, firmware, battery, storage
|
|
133
|
+
pyks2 shoot --af off --wait --download shot.dng
|
|
134
|
+
pyks2 settings # show current settings
|
|
135
|
+
pyks2 settings av=8.0 sv=400 # set them
|
|
136
|
+
pyks2 lists # capability lists (dropdown sources)
|
|
137
|
+
pyks2 browse --limit 20 # list photos
|
|
138
|
+
pyks2 download 100_1507/IMGP1974.DNG --size view -o preview.jpg
|
|
139
|
+
pyks2 liveview -o frame.jpg
|
|
140
|
+
pyks2 watch --resolve # stream camera events live
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Web GUI
|
|
146
|
+
|
|
147
|
+
A browser-based control panel is planned, full Image-Sync parity plus
|
|
148
|
+
extensions (live view, remote capture, touch-to-focus, a settings panel driven
|
|
149
|
+
by the camera's own capability lists, an idle-safe gallery, and event-driven
|
|
150
|
+
updates), served by a thin local backend that reuses this library so it runs on
|
|
151
|
+
any OS in any browser. *Not yet included in this release, the library and CLI
|
|
152
|
+
are the shipping surfaces.*
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## The reverse engineering
|
|
157
|
+
|
|
158
|
+
The heart of this project is the write-up. Highlights:
|
|
159
|
+
|
|
160
|
+
- **The full API is 40 endpoints**, organised as five read *groups*
|
|
161
|
+
(`constants`/`params`/`variables`/`status`/`props`) × four *subsystems*
|
|
162
|
+
(`camera`/`lens`/`liveview`/`device`), plus capture/focus/photo/liveview
|
|
163
|
+
actions and a WebSocket.
|
|
164
|
+
- **Two protocol laws** every client must respect: the real status is in the
|
|
165
|
+
body's `errCode` (not the HTTP status), and datetime/numeric formats are
|
|
166
|
+
inconsistent across endpoints.
|
|
167
|
+
- **Hardware interlocks** mapped and explained: the AF/MF lever, mode dial, and
|
|
168
|
+
movie mode are physical-only; movie mode *disables WiFi entirely*; opening the
|
|
169
|
+
SD-card door kills the connection; and the camera's WiFi access point uses
|
|
170
|
+
client isolation (which shaped how the official app's traffic had to be
|
|
171
|
+
captured).
|
|
172
|
+
- **How Image Sync actually works**, captured off the wire — and why this
|
|
173
|
+
client's event-driven design is better.
|
|
174
|
+
|
|
175
|
+
Start with **[docs/PROTOCOL.md](https://picklerick2005.github.io/PyKS2/PROTOCOL.html)**, then
|
|
176
|
+
**[docs/METHODOLOGY.md](https://picklerick2005.github.io/PyKS2/METHODOLOGY.html)**. The raw evidence is in
|
|
177
|
+
**[examples/](https://github.com/PICKLERICK2005/pyks2/tree/main/examples)** and the machine-readable spec is
|
|
178
|
+
**[examples/API_REFERENCE.json](https://github.com/PICKLERICK2005/pyks2/blob/main/examples/API_REFERENCE.json)**.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Development & tests
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git clone https://github.com/PICKLERICK2005/pyks2.git
|
|
186
|
+
cd pyks2
|
|
187
|
+
pip install -e ".[dev]" # installs pytest, mypy, ruff
|
|
188
|
+
pytest -q # 37 tests, no camera required
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The test suite runs entirely against captured fixtures (`examples/*.json`) via a
|
|
192
|
+
mock camera in `tests/conftest.py`, so it needs no hardware. Tests also serve as
|
|
193
|
+
executable documentation of the API's behaviour — including the trickier
|
|
194
|
+
findings (async capture, the Bulb correction, dynamic capability lists).
|
|
195
|
+
|
|
196
|
+
## Compatibility
|
|
197
|
+
|
|
198
|
+
Verified against a **Pentax K-S2, firmware 01.10**. Other Pentax bodies (K-1,
|
|
199
|
+
KP, K-70, K-3…) share much of this API family but differ in specifics. Running
|
|
200
|
+
the probing approach in [docs/METHODOLOGY.md](https://picklerick2005.github.io/PyKS2/METHODOLOGY.html) on another
|
|
201
|
+
body and sending the diffs is the most useful contribution you could make!
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT — see [LICENSE](https://github.com/PICKLERICK2005/pyks2/blob/main/LICENSE).
|
|
206
|
+
|
|
207
|
+
## Acknowledgements
|
|
208
|
+
|
|
209
|
+
The 2016 K-1 WiFi analysis on the antiguru wiki was a useful starting point for
|
|
210
|
+
hypotheses. Everything here was independently verified against a physical K-S2.
|
|
211
|
+
This project inspects only the camera's own network behaviour and my own hardware;
|
|
212
|
+
it contains no vendor code or any unmentioned references.
|
pyks2-1.0.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# pyks2
|
|
2
|
+
|
|
3
|
+
[](https://github.com/PICKLERICK2005/pyks2/actions/workflows/test.yml)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
[](https://github.com/PICKLERICK2005/pyks2/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
A Python library and CLI for controlling the **Pentax K-S2** over its
|
|
8
|
+
built-in WiFi, built on a complete, hardware-verified reverse-engineering of
|
|
9
|
+
the camera's undocumented HTTP API. (A web GUI is planned; see below.)
|
|
10
|
+
|
|
11
|
+
The K-S2 has a WiFi remote-control API, but Pentax never documented it. This
|
|
12
|
+
project maps the **entire surface** of the K-S2's API against a physical camera,
|
|
13
|
+
writes it up as a proper dissection, and ships a clean client that is lighter
|
|
14
|
+
and more capable than the vendor's own Image Sync app.
|
|
15
|
+
|
|
16
|
+
Visit the pages website .
|
|
17
|
+
|
|
18
|
+
> **Two things make this more than "another camera library":**
|
|
19
|
+
> 1. A full [protocol dissection](https://picklerick2005.github.io/PyKS2/PROTOCOL.html) and
|
|
20
|
+
> [reverse-engineering methodology](https://picklerick2005.github.io/PyKS2/METHODOLOGY.html), every endpoint,
|
|
21
|
+
> every quirk, every hardware limitation, with the raw captures to prove it.
|
|
22
|
+
> 2. A design that **beats the official app**: event-driven via the camera's
|
|
23
|
+
> WebSocket instead of the poll-storm Image Sync uses (~90% of its total
|
|
24
|
+
> requests were just polling one endpoint while interacting with app actively).
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## What's here
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
pyks2/ the library (camera-only HTTP client, typed models, WS events)
|
|
32
|
+
├─ client.py K_S2_WiFi, the API client
|
|
33
|
+
├─ models.py typed response models (defensive parsing)
|
|
34
|
+
├─ events.py /v1/changes WebSocket client (stdlib, zero-dep)
|
|
35
|
+
├─ constants.py endpoints + capability enums
|
|
36
|
+
├─ errors.py typed exceptions (errCode-aware)
|
|
37
|
+
└─ cli.py the command-line interface
|
|
38
|
+
docs/ the reverse-engineering write-up (GitHub Pages source)
|
|
39
|
+
├─ PROTOCOL.md the complete API dissection
|
|
40
|
+
└─ METHODOLOGY.md how it was probed (approach, false trails, traffic capture)
|
|
41
|
+
examples/ real captured JSON responses + the machine-readable API reference
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install pyks2 # or: pip install -e . from a clone
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Join the camera's WiFi (`PENTAX_XXXXXX`), then:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from pyks2 import K_S2_WiFi
|
|
56
|
+
|
|
57
|
+
cam = K_S2_WiFi() # defaults to 192.168.0.1
|
|
58
|
+
assert cam.ping()
|
|
59
|
+
|
|
60
|
+
# take one photo safely: records the baseline, fires, waits for the NEW file,
|
|
61
|
+
# and downloads it all in one call (af="off" is MF-safe: always releases)
|
|
62
|
+
info = cam.capture(af="off", download_to="shot.dng")
|
|
63
|
+
print("captured:", info.path)
|
|
64
|
+
|
|
65
|
+
# change settings (validated by the camera; illegal values raise)
|
|
66
|
+
cam.set_camera_params(av="8.0", sv="400")
|
|
67
|
+
|
|
68
|
+
# browse the card
|
|
69
|
+
for photo in cam.list_photos():
|
|
70
|
+
print(photo.path)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Event-driven, no polling:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
with cam.events() as ev: # /v1/changes WebSocket
|
|
77
|
+
for change in ev:
|
|
78
|
+
if change.is_storage: # a frame just landed
|
|
79
|
+
print("captured:", cam.latest_info().path)
|
|
80
|
+
elif change.is_camera: # a setting changed on the body
|
|
81
|
+
print("settings:", cam.get_camera_params())
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Live view (MJPEG):
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
for jpeg in cam.iter_liveview_frames(max_frames=1):
|
|
88
|
+
open("frame.jpg", "wb").write(jpeg)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## CLI
|
|
94
|
+
|
|
95
|
+
Everything the library does, from a terminal:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pyks2 ping
|
|
99
|
+
pyks2 info # model, firmware, battery, storage
|
|
100
|
+
pyks2 shoot --af off --wait --download shot.dng
|
|
101
|
+
pyks2 settings # show current settings
|
|
102
|
+
pyks2 settings av=8.0 sv=400 # set them
|
|
103
|
+
pyks2 lists # capability lists (dropdown sources)
|
|
104
|
+
pyks2 browse --limit 20 # list photos
|
|
105
|
+
pyks2 download 100_1507/IMGP1974.DNG --size view -o preview.jpg
|
|
106
|
+
pyks2 liveview -o frame.jpg
|
|
107
|
+
pyks2 watch --resolve # stream camera events live
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Web GUI
|
|
113
|
+
|
|
114
|
+
A browser-based control panel is planned, full Image-Sync parity plus
|
|
115
|
+
extensions (live view, remote capture, touch-to-focus, a settings panel driven
|
|
116
|
+
by the camera's own capability lists, an idle-safe gallery, and event-driven
|
|
117
|
+
updates), served by a thin local backend that reuses this library so it runs on
|
|
118
|
+
any OS in any browser. *Not yet included in this release, the library and CLI
|
|
119
|
+
are the shipping surfaces.*
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## The reverse engineering
|
|
124
|
+
|
|
125
|
+
The heart of this project is the write-up. Highlights:
|
|
126
|
+
|
|
127
|
+
- **The full API is 40 endpoints**, organised as five read *groups*
|
|
128
|
+
(`constants`/`params`/`variables`/`status`/`props`) × four *subsystems*
|
|
129
|
+
(`camera`/`lens`/`liveview`/`device`), plus capture/focus/photo/liveview
|
|
130
|
+
actions and a WebSocket.
|
|
131
|
+
- **Two protocol laws** every client must respect: the real status is in the
|
|
132
|
+
body's `errCode` (not the HTTP status), and datetime/numeric formats are
|
|
133
|
+
inconsistent across endpoints.
|
|
134
|
+
- **Hardware interlocks** mapped and explained: the AF/MF lever, mode dial, and
|
|
135
|
+
movie mode are physical-only; movie mode *disables WiFi entirely*; opening the
|
|
136
|
+
SD-card door kills the connection; and the camera's WiFi access point uses
|
|
137
|
+
client isolation (which shaped how the official app's traffic had to be
|
|
138
|
+
captured).
|
|
139
|
+
- **How Image Sync actually works**, captured off the wire — and why this
|
|
140
|
+
client's event-driven design is better.
|
|
141
|
+
|
|
142
|
+
Start with **[docs/PROTOCOL.md](https://picklerick2005.github.io/PyKS2/PROTOCOL.html)**, then
|
|
143
|
+
**[docs/METHODOLOGY.md](https://picklerick2005.github.io/PyKS2/METHODOLOGY.html)**. The raw evidence is in
|
|
144
|
+
**[examples/](https://github.com/PICKLERICK2005/pyks2/tree/main/examples)** and the machine-readable spec is
|
|
145
|
+
**[examples/API_REFERENCE.json](https://github.com/PICKLERICK2005/pyks2/blob/main/examples/API_REFERENCE.json)**.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Development & tests
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
git clone https://github.com/PICKLERICK2005/pyks2.git
|
|
153
|
+
cd pyks2
|
|
154
|
+
pip install -e ".[dev]" # installs pytest, mypy, ruff
|
|
155
|
+
pytest -q # 37 tests, no camera required
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The test suite runs entirely against captured fixtures (`examples/*.json`) via a
|
|
159
|
+
mock camera in `tests/conftest.py`, so it needs no hardware. Tests also serve as
|
|
160
|
+
executable documentation of the API's behaviour — including the trickier
|
|
161
|
+
findings (async capture, the Bulb correction, dynamic capability lists).
|
|
162
|
+
|
|
163
|
+
## Compatibility
|
|
164
|
+
|
|
165
|
+
Verified against a **Pentax K-S2, firmware 01.10**. Other Pentax bodies (K-1,
|
|
166
|
+
KP, K-70, K-3…) share much of this API family but differ in specifics. Running
|
|
167
|
+
the probing approach in [docs/METHODOLOGY.md](https://picklerick2005.github.io/PyKS2/METHODOLOGY.html) on another
|
|
168
|
+
body and sending the diffs is the most useful contribution you could make!
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT — see [LICENSE](https://github.com/PICKLERICK2005/pyks2/blob/main/LICENSE).
|
|
173
|
+
|
|
174
|
+
## Acknowledgements
|
|
175
|
+
|
|
176
|
+
The 2016 K-1 WiFi analysis on the antiguru wiki was a useful starting point for
|
|
177
|
+
hypotheses. Everything here was independently verified against a physical K-S2.
|
|
178
|
+
This project inspects only the camera's own network behaviour and my own hardware;
|
|
179
|
+
it contains no vendor code or any unmentioned references.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Methodology
|
|
3
|
+
nav_order: 3
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How this was reverse-engineered
|
|
7
|
+
|
|
8
|
+
The [protocol dissection](PROTOCOL.md) presents the finished map. This document
|
|
9
|
+
is about *how* the map was made. The approach, the tools, the false trails, and
|
|
10
|
+
the traffic capture to find out how the official Image Sync app works. If you
|
|
11
|
+
want to replicate this on your own non-KS2 Pentax body, start here.
|
|
12
|
+
|
|
13
|
+
There was no documentation to work from. Everything below is the process of
|
|
14
|
+
turning a black-box camera into a fully characterised API.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Principle: confirmed, not inferred
|
|
19
|
+
|
|
20
|
+
The guiding rule throughout: **a behaviour isn't "known" until the hardware
|
|
21
|
+
demonstrates it.** The official Pentax Image Sync android app, forum posts, and a
|
|
22
|
+
partial 2016 write-up of the related K-1 on the pentax forums were useful for coming up with
|
|
23
|
+
hypotheses, but every claim in the final map rests on a response captured from the actual camera body.
|
|
24
|
+
Several "facts" inherited from prior assumptions turned out to be wrong (see the `/v1/photos`
|
|
25
|
+
myth below), which is exactly why the this rule and methedology mattered most throughout
|
|
26
|
+
this project. The KS2 being very different from it's predecessors (post-ricoh acquisition)
|
|
27
|
+
is the main reason why this whole project was initiated, not to mention the fact that the
|
|
28
|
+
already-existing PKTRIGGERCORD does not support the KS2.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Stage 1: find the surface
|
|
33
|
+
|
|
34
|
+
The break came from `GET /v1/apis`, which the camera answers with a list of all
|
|
35
|
+
its own endpoint templates. That turned an open-ended search into a finite
|
|
36
|
+
checklist of ~40 endpoints to characterise.
|
|
37
|
+
|
|
38
|
+
From there, the read endpoints were easy, and pleasantly so! because the
|
|
39
|
+
camera serves plain JSON that a browser renders directly (all hail firefox).
|
|
40
|
+
Navigating to `http://192.168.0.1/v1/props`, `/v1/constants/camera`, and so on in
|
|
41
|
+
Firefox showed each response formatted and readable, no tooling required. Hitting
|
|
42
|
+
each `/v1/{group}/{facet}` combination and diffing the results is where the
|
|
43
|
+
five-groups-×-four-facets structure became clear.
|
|
44
|
+
|
|
45
|
+
## Stage 2: an automated scrubber
|
|
46
|
+
|
|
47
|
+
Hitting 40 endpoints by hand doesn't scale and isn't fun, so the
|
|
48
|
+
probing was scripted. A single sequenced scrubber:
|
|
49
|
+
|
|
50
|
+
- fired every endpoint in a defined order,
|
|
51
|
+
- captured status, headers, timing, and body for each,
|
|
52
|
+
- wrote every response to disk (the curated results became
|
|
53
|
+
[`examples/`](../examples/)),
|
|
54
|
+
- snapshotted the camera's settings before write-tests and restored them after
|
|
55
|
+
(so probing never left the camera in a weird state),
|
|
56
|
+
- and paused at the handful of probes that need physcial changes to the camera body or observations.
|
|
57
|
+
|
|
58
|
+
That last point became a core technique, using claude's help to make the scrubber script and instead
|
|
59
|
+
focusing on physically manipulating the camera when prompted.
|
|
60
|
+
|
|
61
|
+
The main methodology for these automated scripts is: human drives the hardware;
|
|
62
|
+
the script reads and correlates and logs any changes.**
|
|
63
|
+
|
|
64
|
+
This is how the event vocabulary was mapped. With the `/v1/changes` WebSocket
|
|
65
|
+
open, the script prompted through a scripted sequence of physical actions: "turn
|
|
66
|
+
the mode dial," "half-press the shutter," "flip the AF/MF lever," "open the SD
|
|
67
|
+
door" etc... and logged which `changed` events each action produced.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Stage 5: capturing traffic from Pentax's Image Sync app on android
|
|
72
|
+
|
|
73
|
+
One question resisted every direct probe: how does the official app populate its
|
|
74
|
+
image gallery grid efficiently given that `?size=thumb` returns `400`?
|
|
75
|
+
The only way to know for certain was to watch the app's own traffic.
|
|
76
|
+
Because the camera speaks plain HTTP and does not verify it's being controlled by the
|
|
77
|
+
app whatsoever, no TLS interception was needed, only running an android phone's traffic through
|
|
78
|
+
a machine running mitmproxy was needed.
|
|
79
|
+
|
|
80
|
+
Although I was able to connect more than one device to the camera's hotspot,
|
|
81
|
+
**The camera's WiFi access point uses client isolation.** With the phone and a
|
|
82
|
+
PC both joined to the camera's network, so they cannot see each other, every
|
|
83
|
+
attempt to route the phone's traffic through a proxy on the PC timed out.
|
|
84
|
+
The PC could reach the camera (`192.168.0.1`) but not the phone on the same network.
|
|
85
|
+
Device-to-device traffic is blocked at the AP.
|
|
86
|
+
|
|
87
|
+
The fix was to change the topology so the phone→PC link doesn't go through the
|
|
88
|
+
camera at all:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Phone ──(PC's own hotspot)──> Windows Computer ──(camera WiFi)──> Camera
|
|
92
|
+
│
|
|
93
|
+
proxy sits here
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The PC hosts a WiFi hotspot, the phone joins *that* (no isolation on the
|
|
97
|
+
PC's own network), and the PC bridges to the camera over its separate
|
|
98
|
+
WiFi connection with the proxy in the middle. Right away, even before setting up
|
|
99
|
+
the proxy, just by having the android device connected to the PC's hotspot which was
|
|
100
|
+
in turn connected to the KS2's hotspot, the Image Sync app was able to control the camera
|
|
101
|
+
just fine.
|
|
102
|
+
|
|
103
|
+
With the phone's proxy pointed at the PC, the app's requests were thoroughly recorded and
|
|
104
|
+
used to verify earlier findings. The gallery turned out to use exactly the documented endpoints
|
|
105
|
+
(`/v1/photos` →per-file `/info` → `?size=view` thumbnails), and the capture also revealed the
|
|
106
|
+
app's poll-heavy architecture (618 of 691 requests were `/v1/props` over a couple minutes).
|
|
107
|
+
No secret endpoints, just confirmation, plus the architectural insight that `pyks2` could
|
|
108
|
+
do better with the WebSocket due to the native app's poor performance.
|
|
109
|
+
|
|
110
|
+
> This technique was the least intrusive and didn't decompile or direclty modify any vendor software.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Replicating on your own body
|
|
115
|
+
|
|
116
|
+
1. Enable the camera's WiFi and join its network (`PENTAX_XXXXXX`).
|
|
117
|
+
2. Confirm reachability by opening `http://192.168.0.1/v1/ping` in a browser
|
|
118
|
+
the camera serves readable JSON, so most reads can be explored this way.
|
|
119
|
+
3. Walk the `/v1/{group}/{facet}` endpoints and the `/v1/apis` list; capture
|
|
120
|
+
each response. Scripting this (fire in sequence, save body + headers +
|
|
121
|
+
timing, snapshot/restore settings around any write-tests, and pause for
|
|
122
|
+
operator observation on the physical probes) makes it reproducible on different
|
|
123
|
+
bodies or firmware versions.
|
|
124
|
+
4. Compare your captures against [`examples/`](../examples/). Different bodies
|
|
125
|
+
(K-1, KP, K-70) share this API family and will most likely differ in multiple ways
|
|
126
|
+
contributions documenting those differences are more than welcome!
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
*Back to the [protocol dissection](PROTOCOL.md), or on to the library and CLI in
|
|
131
|
+
the [README](../README.md).*
|