Skip to content

Commit 9b2a610

Browse files
committed
docs: initial API docs
1 parent eaf31a2 commit 9b2a610

File tree

3 files changed

+184
-3
lines changed

3 files changed

+184
-3
lines changed

docs/index.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,62 @@
11
# Wokwi Python Client Library
22

3-
Welcome! These docs will eventually cover installation, quickstart examples, and the full API reference.
3+
Typed, asyncio-friendly Python SDK for the **Wokwi Simulation API**.
4+
5+
## Features
6+
7+
- Connect to the Wokwi Simulator from Python
8+
- Upload diagrams and firmware files
9+
- Start, pause, resume, and restart simulations
10+
- Monitor serial output asynchronously
11+
- Fully type-annotated and easy to use with asyncio
12+
13+
## Installation
14+
15+
Requires Python ≥ 3.9
16+
17+
```bash
18+
pip install wokwi-client
19+
```
20+
21+
## Getting an API Token
22+
23+
Get your API token from [https://wokwi.com/dashboard/ci](https://wokwi.com/dashboard/ci).
24+
25+
## Quickstart Example
26+
27+
```python
28+
import asyncio
29+
import os
30+
from wokwi_client import WokwiClient, GET_TOKEN_URL
31+
32+
33+
async def main():
34+
token = os.getenv("WOKWI_CLI_TOKEN")
35+
if not token:
36+
raise SystemExit(
37+
f"Set WOKWI_CLI_TOKEN in your environment. You can get it from {GET_TOKEN_URL}."
38+
)
39+
40+
client = WokwiClient(token)
41+
await client.connect()
42+
await client.upload_file("diagram.json")
43+
await client.upload_file("firmware.bin")
44+
await client.upload_file("firmware.elf")
45+
await client.start_simulation(firmware="firmware.bin", elf="firmware.elf")
46+
serial_task = asyncio.create_task(
47+
client.serial_monitor_cat()
48+
) # Stream serial output
49+
await client.wait_until_simulation_time(10) # Run simulation for 10 seconds
50+
serial_task.cancel()
51+
await client.disconnect()
52+
53+
54+
if __name__ == "__main__":
55+
asyncio.run(main())
56+
```
57+
58+
See the [examples/hello_esp32/main.py](https://github.com/wokwi/wokwi-python-client/blob/main/examples/hello_esp32/main.py) for a full example including serial monitoring.
59+
60+
## API Reference
61+
62+
See the [API Reference](reference/wokwi_client.md) for full details.

src/wokwi_client/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
"""
2+
Wokwi Python Client Library
3+
4+
Typed, asyncio-friendly Python SDK for the Wokwi Simulation API.
5+
6+
Provides the WokwiClient class for connecting to, controlling, and monitoring Wokwi simulations from Python.
7+
"""
8+
19
# SPDX-FileCopyrightText: 2025-present CodeMagic LTD
210
#
311
# SPDX-License-Identifier: MIT

src/wokwi_client/client.py

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,142 @@
1616

1717

1818
class WokwiClient:
19+
"""
20+
Asynchronous client for the Wokwi Simulation API.
21+
22+
This class provides methods to connect to the Wokwi simulator, upload files, control simulations,
23+
and monitor serial output. It is designed to be asyncio-friendly and easy to use in Python scripts
24+
and applications.
25+
"""
26+
1927
version: str
2028
last_pause_nanos: int
2129

2230
def __init__(self, token: str, server: Optional[str] = None):
31+
"""
32+
Initialize the WokwiClient.
33+
34+
Args:
35+
token: API token for authentication (get from https://wokwi.com/dashboard/ci).
36+
server: Optional custom server URL. Defaults to the public Wokwi server.
37+
"""
2338
self.version = get_version()
2439
self._transport = Transport(token, server or DEFAULT_WS_URL)
2540
self.last_pause_nanos = 0
2641
self._transport.add_event_listener("sim:pause", self._on_pause)
2742
self._pause_queue = EventQueue(self._transport, "sim:pause")
2843

2944
async def connect(self) -> dict[str, Any]:
45+
"""
46+
Connect to the Wokwi simulator server.
47+
48+
Returns:
49+
A dictionary with server information (e.g., version).
50+
"""
3051
return await self._transport.connect()
3152

3253
async def disconnect(self) -> None:
54+
"""
55+
Disconnect from the Wokwi simulator server.
56+
"""
3357
await self._transport.close()
3458

3559
async def upload(self, name: str, content: bytes) -> ResponseMessage:
60+
"""
61+
Upload a file to the simulator from bytes content.
62+
63+
Args:
64+
name: The name to use for the uploaded file.
65+
content: The file content as bytes.
66+
67+
Returns:
68+
The response message from the server.
69+
"""
3670
return await upload(self._transport, name, content)
3771

3872
async def upload_file(
3973
self, filename: str, local_path: Optional[Path] = None
4074
) -> ResponseMessage:
75+
"""
76+
Upload a local file to the simulator.
77+
78+
Args:
79+
filename: The name to use for the uploaded file.
80+
local_path: Optional path to the local file. If not provided, uses filename as the path.
81+
82+
Returns:
83+
The response message from the server.
84+
"""
4185
return await upload_file(self._transport, filename, local_path)
4286

43-
async def start_simulation(self, **kwargs: Any) -> ResponseMessage:
44-
return await start(self._transport, **kwargs)
87+
async def start_simulation(
88+
self,
89+
firmware: str,
90+
elf: str,
91+
pause: bool = False,
92+
chips: list[str] = [],
93+
) -> ResponseMessage:
94+
"""
95+
Start a new simulation with the given parameters.
96+
97+
The firmware and ELF files must be uploaded to the simulator first using the
98+
`upload()` or `upload_file()` methods. The firmware and ELF files are required
99+
for the simulation to run.
100+
101+
The optional `chips` parameter can be used to load custom chips into the simulation.
102+
For each custom chip, you need to upload two files:
103+
- A JSON file with the chip definition, called `<chip_name>.chip.json`.
104+
- A binary file with the chip firmware, called `<chip_name>.chip.bin`.
105+
106+
For example, to load the `inverter` chip, you need to upload the `inverter.chip.json`
107+
and `inverter.chip.bin` files. Then you can pass `["inverter"]` to the `chips` parameter,
108+
and reference it in your diagram.json file by adding a part with the type `chip-inverter`.
109+
110+
Args:
111+
firmware: The firmware binary filename.
112+
elf: The ELF file filename.
113+
pause: Whether to start the simulation paused (default: False).
114+
chips: List of custom chips to load into the simulation (default: empty list).
115+
116+
Returns:
117+
The response message from the server.
118+
"""
119+
return await start(
120+
self._transport,
121+
firmware=firmware,
122+
elf=elf,
123+
pause=pause,
124+
chips=chips,
125+
)
45126

46127
async def pause_simulation(self) -> ResponseMessage:
128+
"""
129+
Pause the running simulation.
130+
131+
Returns:
132+
The response message from the server.
133+
"""
47134
return await pause(self._transport)
48135

49136
async def resume_simulation(self, pause_after: Optional[int] = None) -> ResponseMessage:
137+
"""
138+
Resume the simulation, optionally pausing after a given number of nanoseconds.
139+
140+
Args:
141+
pause_after: Number of nanoseconds to run before pausing again (optional).
142+
143+
Returns:
144+
The response message from the server.
145+
"""
50146
return await resume(self._transport, pause_after)
51147

52148
async def wait_until_simulation_time(self, seconds: float) -> None:
149+
"""
150+
Pause and resume the simulation until the given simulation time (in seconds) is reached.
151+
152+
Args:
153+
seconds: The simulation time to wait for, in seconds.
154+
"""
53155
await pause(self._transport)
54156
remaining_nanos = seconds * 1e9 - self.last_pause_nanos
55157
if remaining_nanos > 0:
@@ -58,9 +160,21 @@ async def wait_until_simulation_time(self, seconds: float) -> None:
58160
await self._pause_queue.get()
59161

60162
async def restart_simulation(self, pause: bool = False) -> ResponseMessage:
163+
"""
164+
Restart the simulation, optionally starting paused.
165+
166+
Args:
167+
pause: Whether to start the simulation paused (default: False).
168+
169+
Returns:
170+
The response message from the server.
171+
"""
61172
return await restart(self._transport, pause)
62173

63174
async def serial_monitor_cat(self) -> None:
175+
"""
176+
Print serial monitor output to stdout as it is received from the simulation.
177+
"""
64178
async for line in monitor_lines(self._transport):
65179
print(line.decode("utf-8"), end="", flush=True)
66180

0 commit comments

Comments
 (0)