Skip to content

Conversation

@llogen
Copy link
Contributor

@llogen llogen commented Dec 8, 2025

The PiKVM module provides comprehensive control of a DUT via a PiKVM device. It offers power management through ATX control, keyboard input simulation, and virtual media mounting capabilities.

Power management commands: on, off, force-off, reset, force-reset,
and status. Keyboard control commands: type, key, combo, and paste.
Virtual media commands: mount, mount-url, unmount, and media-status.

The module connects to a PiKVM device using HTTP/HTTPS with configurable host, user, password, and timeout. It supports both short and long ATX button presses for power and reset control, allows sending keyboard input and key combinations, and enables mounting ISO images from local files or URLs.

resolves #246

@llogen llogen force-pushed the feat/addPiKVModule branch 2 times, most recently from 9dbc7d7 to ad77246 Compare December 8, 2025 10:22
Copy link
Contributor

@RiSKeD RiSKeD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice changes, looks pretty clean overall. Just some thoughts:

  • Maybe separate the power,virtual-media,keyboard functionalities into separate files for better readability
  • 'combo' -> key-combo

Tell me what you think 😄

@llogen llogen force-pushed the feat/addPiKVModule branch from ad77246 to ae564e7 Compare December 17, 2025 10:27
@llogen
Copy link
Contributor Author

llogen commented Dec 17, 2025

Nice changes, looks pretty clean overall. Just some thoughts:

  • Maybe separate the power,virtual-media,keyboard functionalities into separate files for better readability
  • 'combo' -> key-combo

Tell me what you think 😄

I think thats a good idea :)

@llogen llogen force-pushed the feat/addPiKVModule branch from ae564e7 to 4e04fe1 Compare December 17, 2025 12:26
@llogen llogen force-pushed the feat/addPiKVModule branch 3 times, most recently from 441f7ee to 2183a74 Compare January 6, 2026 14:53
The PiKVM module provides comprehensive control of a DUT via a
PiKVM device. It offers power management through ATX control,
keyboard input simulation, and virtual media mounting capabilities.

Power management commands: on, off, force-off, reset, force-reset,
and status. Keyboard control commands: type, key, combo, and paste.
Virtual media commands: mount, mount-url, unmount, and media-status.

The module connects to a PiKVM device using HTTP/HTTPS with
configurable host, user, password, and timeout. It supports both
short and long ATX button presses for power and reset control,
allows sending keyboard input and key combinations, and enables
mounting ISO images from local files or URLs.

Signed-off-by: llogen <[email protected]>
@llogen llogen force-pushed the feat/addPiKVModule branch from 2183a74 to 9454670 Compare January 6, 2026 14:55
Copy link
Member

@jenstopp jenstopp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, full KVM feature set implemented 🚀 :)

@@ -0,0 +1,167 @@
# PiKVM

This module provides comprehensive control of a DUT via a PiKVM device. It offers power management through ATX control, keyboard input simulation, and virtual media mounting capabilities.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a link to PiKVM for further info? In case someone does not know this device. Also ist is desinged to support a special PiKVM device?

Comment on lines +26 to +27
key <keyname> Send a single key (e.g., Enter, Escape, F12)
key-combo <keys> Send key combination (e.g., Ctrl+Alt+Delete)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write or refer to a full list of available 'key strings'

Comment on lines +55 to +68
This module interacts with the following PiKVM API endpoints:

- `/api/atx` - Get ATX power status
- `/api/atx/power` - Intelligent power management (on/off/off_hard/reset_hard)
- `/api/atx/click` - ATX button control (reset)
- `/api/hid/print` - Type text input
- `/api/hid/events/send_key` - Send keyboard keys and combinations
- `/api/msd` - Mass Storage Device (virtual media) status
- `/api/msd/write` - Upload images to PiKVM storage
- `/api/msd/write_remote` - Download images from URL to PiKVM
- `/api/msd/set_params` - Configure virtual media parameters
- `/api/msd/set_connected` - Mount/unmount media
- `/api/msd/remove` - Delete images from storage
- `/api/streamer/snapshot` - Capture screenshot
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would a user need this info for?

| user | string | admin | Username for authentication |
| password | string | - | Password for authentication |
| timeout | string | 10s | Timeout for HTTP requests (e.g., "10s", "30s") |
| command | string | - | **Required**: Command type ("power", "keyboard", "media", "screenshot") |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is only one supported at a time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes you can only define one command per command. @RiSKeD and I talked about making the command parameter optional to also allow full control just via arguments.

Comment on lines +74 to +144
### Basic Power Control

```yaml
version: 0
devices:
my-server:
desc: "Server controlled via PiKVM"
cmds:
power:
desc: "Power management: on|off|force-off|reset|force-reset|status"
modules:
- module: pikvm
main: true
options:
host: https://pikvm.local
user: admin
password: admin
command: power
```

### Boot Menu Access

```yaml
keyboard:
desc: "Keyboard control: type <text>|key <keyname>|key-combo <combo>"
modules:
- module: pikvm
main: true
options:
host: https://pikvm.local
user: admin
password: admin
command: keyboard

# Usage: dutctl my-server keyboard key F12
```

### ISO Mounting

```yaml
media:
desc: "Virtual media control: mount <path>|mount-url <url>|unmount|media-status"
modules:
- module: pikvm
main: true
options:
host: https://pikvm.local
user: admin
password: admin
command: media

# Usage: dutctl my-server media mount-url https://releases.ubuntu.com/22.04/ubuntu-22.04-live-server-amd64.iso
```

### Screenshot Capture

```yaml
screenshot:
desc: "Capture a screenshot from PiKVM"
modules:
- module: pikvm
main: true
options:
host: https://pikvm.local
user: admin
password: admin
command: screenshot

# Usage: dutctl my-server screenshot
# The screenshot will be saved to the current directory
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put all examples in YAML files and link them here for better overview

Comment on lines +140 to +148
if p.Command == "" {
return fmt.Errorf("pikvm: command option is required (must be: %s, %s, %s, or %s)",
cmdTypePower, cmdTypeKeyboard, cmdTypeMedia, cmdTypeScreenshot)
}

if !isValidCommand(p.Command) {
return fmt.Errorf("pikvm: invalid command %q (must be: %s, %s, %s, or %s)",
p.Command, cmdTypePower, cmdTypeKeyboard, cmdTypeMedia, cmdTypeScreenshot)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combine in one function

Comment on lines +182 to +185
host := strings.TrimSpace(p.Host)
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = "https://" + host
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combine with the host validation part from above

case cmdTypeScreenshot:
return p.handleScreenshot(ctx, s)
default:
return fmt.Errorf("pikvm: unknown command type %q", p.Command)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try to show that it is not the users fault:

Suggested change
return fmt.Errorf("pikvm: unknown command type %q", p.Command)
return fmt.Errorf("pikvm: misconfigured: unknown command type %q", p.Command)
Suggested change
return fmt.Errorf("pikvm: unknown command type %q", p.Command)
return fmt.Errorf("pikvm: invalid configuration: unknown command type %q", p.Command)

Comment on lines +231 to +233
func (p *PiKVM) doRequest(ctx context.Context, method, urlPath string, body io.Reader, contentType string) (*http.Response, error) {
return p.doRequestWithOptions(ctx, method, urlPath, body, contentType, requestOptions{})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for testing purposes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for the purpose of not having 3 different methods and combine them within one with the requestOptions input

Comment on lines +301 to +313
func (p *PiKVM) buildRequestURL(urlPath string) (string, error) {
targetURL := *p.baseURL

parsedPath, err := url.Parse(urlPath)
if err != nil {
return "", fmt.Errorf("invalid URL path: %v", err)
}

targetURL.Path = path.Join(targetURL.Path, parsedPath.Path)
targetURL.RawQuery = parsedPath.RawQuery

return targetURL.String(), nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this done during init?

)

const (
keyDelay = 500 * time.Millisecond
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe default this and make the delay an option instead?

}

// Check for invalid characters
for _, c := range []string{invalidCharNul, invalidCharNewline, invalidCharReturn} {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would maybe suggest to use a precompiled regex instead

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or an array of values

contentType string,
) (*http.Response, error) {
return p.doRequestWithOptions(ctx, method, urlPath, body, contentType, requestOptions{noTimeout: true})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of this wrappers, just call the function with options directly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PiKVM Module Proposal

4 participants