There are two hardware families:
- non-Pro: the original NanoKVM (hardware Alpha and Beta) and the NanoKVM PCIe
- Pro: NanoKVM Pro
Many endpoints exist on only one family or need a minimum application version.
Each method declares what it needs. The client checks it and raises
NanoKVMNotSupportedError without contacting the device:
from nanokvm.client import NanoKVMNotSupportedError
try:
await client.get_swap_size()
except NanoKVMNotSupportedError as error:
print(error) # get_swap_size requires hardware: non-Pro (detected: Pro)The hardware is detected at login, or on the first call when the client
starts from a stored token. Firmware versions are detected on the first call
that needs them, and kept until the session resets. After updating the firmware
on a client that stays logged in, call await client.detect_versions().
Version strings that are not plain numbers, such as development builds, count
as supported.
client.hw_version, client.application_version and client.image_version
expose what was detected.
Every method below is a coroutine on NanoKVMClient. The requirements column
uses these abbreviations:
- all: every device
- non-Pro 2.3.1: non-Pro with application 2.3.1 or newer, and not the Pro
- non-Pro 2.2.6 / Pro: non-Pro 2.2.6 or newer, or any Pro
- PCIe: NanoKVM PCIe only
Request and response models live in nanokvm.models.
| Methods | Requires |
|---|---|
get_info, get_hardware, get_gpio, push_button |
all |
get_oled_info, set_oled_sleep |
all |
get_virtual_device_status, update_virtual_device |
all |
get_hostname, set_hostname, get_web_title, set_web_title |
non-Pro 2.2.6 / Pro |
get_ssh_state, enable_ssh, disable_ssh |
non-Pro 2.1.6 / Pro |
get_mdns_state, enable_mdns, disable_mdns |
non-Pro 2.2.2 / Pro |
reboot_system |
non-Pro 2.2.2 / Pro |
set_tls |
non-Pro 2.2.7 |
get_swap_size, set_swap_size |
non-Pro 2.2.6 |
get_memory_limit, set_memory_limit |
non-Pro |
switch_to_pikvm, get_led_strip, set_led_strip |
Pro |
get_lcd_time_format, set_lcd_time_format |
Pro 1.1.6 |
get_timezone, set_timezone, get_time_status, sync_time |
Pro 1.1.6 |
get_time |
Pro 1.2.6 |
get_low_power, set_low_power, refresh_virtual_device |
Pro 1.2.7 |
get_menubar_config, set_menubar_config |
Pro 1.2.10 |
set_tls restarts the device's web server, which then answers only on the
other scheme (http or https), within about half a minute.
push_button(button, duration_ms) holds the power or reset button for
duration_ms; 0 presses for the firmware default of 800 ms. The device
answers once the button is released, so the call waits for the press on top of
request_timeout. Holding the power button forces a hung target off:
await client.push_button(GpioType.POWER, 6000) # hold: force off
async with asyncio.timeout(30):
while (await client.get_gpio()).pwr: # power LED
await asyncio.sleep(1)
await client.push_button(GpioType.POWER, 800) # short press: power onCheck the power LED rather than the video signal: on the PCIe,
get_hdmi_state().signal can stay True for about ten seconds after the
target is off.
| Methods | Requires |
|---|---|
paste_text |
all |
key_down, key_up, key_tap |
all, see below |
mouse_move_abs, mouse_move_rel, mouse_down, mouse_up, mouse_click, mouse_scroll |
all |
get_hid_mode, set_hid_mode |
non-Pro 2.2.5 / Pro |
reset_hid |
non-Pro / Pro 1.1.6 |
get_mouse_jiggler_state, set_mouse_jiggler_state |
non-Pro 2.2.6 / Pro |
get_shortcuts, add_shortcut, delete_shortcut |
non-Pro 2.3.2 / Pro 1.2.8 |
get_leader_key, set_leader_key |
non-Pro 2.3.4 / Pro 1.2.12 |
get_keyboard_led_status |
non-Pro 2.5.0 |
paste_text accepts only the characters in nanokvm.client.PASTE_CHAR_MAP.
The key methods send USB HID key codes, such as KeyCode.F12 or any int,
with modifiers such as KeyModifier.LEFT_CTRL:
from nanokvm.models import KeyCode, KeyModifier
await client.key_tap(
KeyCode.DELETE, modifiers=KeyModifier.LEFT_CTRL | KeyModifier.LEFT_ALT
)key_downholds up to six keys and replaces the keys held before.key_upreleases them all.key_tappresses and releases, and still releases when it is cancelled.- Before non-Pro 2.3.2 and Pro 1.2.6, the device holds one key at a time and
applies modifiers only together with a key. Other reports raise
NanoKVMNotSupportedError. - The keyboard and mouse share one WebSocket. While it is open, the client sends a heartbeat every 10 seconds, as the web UI does, so that non-Pro 2.5.0 and newer do not close it when idle.
To catch a short boot-menu window, tap a key repeatedly:
loop = asyncio.get_running_loop()
end = loop.time() + 8
while loop.time() < end:
await client.key_tap(KeyCode.F12)
await asyncio.sleep(0.4)| Methods | Requires |
|---|---|
mjpeg_stream, mjpeg_stream_bytes, open_ws, open_webrtc_signaling |
all |
get_snapshot |
non-Pro, or Pro with interrupt_webrtc=True |
set_screen, set_frame_detect, pause_frame_detect |
non-Pro |
get_input_region, set_input_region, get_input_resolution |
non-Pro 2.5.1 |
reset_hdmi |
PCIe 2.1.5 |
get_hdmi_state, enable_hdmi, disable_hdmi |
PCIe 2.2.8 |
set_hdmi_idle_timeout |
PCIe 2.5.0 |
set_stream_mode, set_stream_quality, set_gop |
Pro |
get_hdmi_capture, set_hdmi_capture, get_hdmi_passthrough, set_hdmi_passthrough |
Pro |
get_edid, switch_edid |
Pro |
get_custom_edid_list, upload_edid, delete_edid |
Pro 1.2.3 |
set_rate_control_mode |
Pro 1.2.6 |
set_fps |
Pro 1.2.8 |
mjpeg_stream() yields decoded PIL.Image frames, mjpeg_stream_bytes() the
raw JPEG bytes, and get_snapshot() the first one. open_webrtc_signaling()
returns the H.264 WebRTC signaling session for the detected hardware.
Video streaming covers both, and why the Pro refuses
snapshots by default.
open_ws(path) opens a new WebSocket on a path relative to the API base, such
as stream/h264/webrtc, for protocols the client does not wrap. The caller
closes it before the client exits.
| Methods | Requires |
|---|---|
get_images, get_mounted_image, mount_image |
all |
upload_image |
non-Pro 2.3.1 / Pro, see below |
delete_image |
non-Pro 2.3.0 / Pro 1.1.6 |
download_image, get_image_download_status, is_image_download_enabled, watch_image_download |
non-Pro 2.1.6 / Pro |
cancel_image_download |
non-Pro 2.5.0 |
get_cdrom_status |
non-Pro 2.2.1 |
get_scripts, upload_script, run_script, delete_script |
all |
get_autostart_scripts, get_autostart_script, upload_autostart_script, delete_autostart_script |
non-Pro 2.3.1 |
Mount an image on the host as a USB disk, or as a CD-ROM with cdrom=True:
images = await client.get_images() # images.files: ["/data/debian.iso", ...]
await client.mount_image("/data/debian.iso", cdrom=True)
mounted = await client.get_mounted_image() # mounted.file: "/data/debian.iso"
await client.mount_image() # unmountget_images()lists the.isoand.imgfiles on the device by the full path thatmount_image()takes.get_mounted_image().fileis empty when no image is mounted.mount_image()without a file unmounts. On non-Pro devices, the host then sees the device's own/datapartition as the USB disk. To remove the disk,update_virtual_device(VirtualDevice.DISK)toggles it, andget_virtual_device_status().diskreports whether it is on.read_only=Trueneeds Pro 1.2.1 or newer.- Mounting and unmounting reconnect the host's USB devices, including the keyboard and mouse. The Pro answers about 3 seconds later.
- Non-Pro firmware before 2.5.2 keeps CD-ROM mode when a disk image is mounted right after a CD-ROM image. Unmount first. From 2.5.2, the host can only read a mounted image.
Upload an image with progress reporting, then mount it:
def on_progress(progress):
print(f"{progress.percentage:.0f}%")
await client.upload_image("debian.iso", progress_callback=on_progress)
await client.mount_image("/data/debian.iso", cdrom=True)- Non-Pro devices need application 2.3.1 or newer and accept
.isofiles with ASCII names. On 2.5.0 or newer, passsha256=to have the device verify the upload. - The Pro accepts
.isoand.imgfiles. - An existing image with the same name raises
FileExistsErrorunlessoverwrite=True. On the Pro, overwriting deletes the old image before the upload starts, so a failed upload leaves neither copy.
Or have the device download the image itself (sha256= needs non-Pro 2.5.0):
await client.download_image("https://example.com/debian.iso")
async for status in client.watch_image_download():
print(status.status, status.percentage)Autostart scripts run when the device boots.
| Methods | Requires |
|---|---|
get_wifi_status, connect_wifi_no_auth |
all |
send_wake_on_lan, get_wol_macs, delete_wol_mac |
all |
set_wol_mac_name |
non-Pro 2.2.6 / Pro |
connect_wifi, disconnect_wifi |
non-Pro 2.3.1 / Pro |
verify_ap_login |
non-Pro 2.3.6 / Pro 1.2.14 |
get_dns, set_dns |
non-Pro 2.4.1 |
scan_wifi |
Pro 1.1.7 |
get_static_ip, set_static_ip |
Pro 1.2.3 |
| Methods | Requires |
|---|---|
get_application_version, update_application, install_application_update |
all, see below |
get_preview_status, set_preview_state |
non-Pro 2.2.5 / Pro |
update_application_offline |
non-Pro 2.3.1 |
get_update_server, set_update_server |
non-Pro 2.5.1 |
get_tailscale_status, tailscale_install, tailscale_uninstall, tailscale_up, tailscale_down, tailscale_login, tailscale_logout, tailscale_start, tailscale_stop, tailscale_restart |
non-Pro 2.1.6 / Pro |
get_mcp_config, set_mcp_enabled, regenerate_mcp_api_key |
non-Pro 2.5.0 |
get_ai_control_status, set_ai_control_mode |
non-Pro 2.5.0 |
assistant_install, assistant_start |
Pro 1.1.4 |
kvmadmin_install, kvmadmin_uninstall, kvmadmin_start, kvmadmin_stop, kvmadmin_status |
Pro 1.1.5 |
install_application_update() updates the application when the device is
out of date, and waits until it runs the new version, which it returns. The
update restarts the device's server and ends the session, so the client logs
in again with credentials kept by keep_credentials=True (see
Connecting):
async with NanoKVMClient(url, keep_credentials=True) as client:
await client.authenticate("username", "password")
version = await client.install_application_update()- Without kept credentials it raises
NanoKVMNotAuthenticatedErrorbefore contacting the device. It raisesNanoKVMErrorwhen the device cannot reach its update server, andTimeoutErrorwhen the new version is not running withintimeout(default 10 minutes). get_application_version()returns thecurrentandlatestversions.latestis empty when the device cannot reach its update server. The device asks the server while answering, so the call waits up to 60 seconds.update_application()answers once the update is installed, which can take minutes; it waits up to 15 minutes. It does not wait for the restart. Non-Pro firmware before 2.5.1 restarts before it answers, so the call returns when the connection drops, without confirming the update; the same applies toupdate_application_offline().install_application_update()confirms it.- With
set_preview_state(True), the device installs preview releases.
update_application_offline("nanokvm_2.5.1.tar.gz") installs an official
package from a local file. Pass sha256= to check the file first; non-Pro
2.5.1 and newer also check it on the device.
| Methods | Requires |
|---|---|
authenticate, logout, get_account, change_password, is_password_updated |
all |
get_users, create_user, update_user, delete_user, set_user_password |
non-Pro 2.5.1 |