The normal reusable API is the Go package at the module root:
import controller "pccontroller.local/controller"For other languages, cmd/controller-cabi exposes a deliberately small,
language-neutral JSON ABI:
char *PCControllerInvoke(char *request_json);
void PCControllerFree(char *response_json);Build it from the project root with the canonical host packager (which also tests the exported ABI):
build.cmd --host-onlyFor a developer-only CMake verification build that produces only the library, generated header, and external C consumer smoke tests, configure the Go module itself:
cmake -S Tools/Controller -B .build/controller-cabi -G Ninja ^
-DPCCONTROLLER_CABI_CC=C:\path\to\x86_64-w64-mingw32-gcc.exe
cmake --build .build/controller-cabi --target pccontroller_cabi_installed_smoke
ctest --test-dir .build/controller-cabi --output-on-failure
cmake --install .build/controller-cabi --prefix .build/controller-cabi/installThe CMake target delegates directly to go build -buildmode=c-shared; it does
not replace the canonical package/provisioning tool. On Windows it verifies
that the same compiler selected for CMake and CGO matches go env GOARCH,
advertises native MinGW-w64 macros, and is neither MSYS nor Cygwin. Point
PCCONTROLLER_CABI_CC at a native UCRT/MinGW-w64 compiler on a fresh build
directory. The regular host packager remains the preferred path when automatic
compiler discovery or provisioning is wanted. CMake output is deliberately
not a deployable host package: it omits the canonical manifest, embedded
defaults, product resources, package verification, and deployment lifecycle.
On Linux, the same developer-only CMake/CTest smoke uses the native system C compiler by default:
cmake -S Tools/Controller -B .build/controller-cabi -G Ninja -DBUILD_TESTING=ON
cmake --build .build/controller-cabi --target pccontroller_cabi_installed_smoke
ctest --test-dir .build/controller-cabi --output-on-failureGo emits both bin/pccontroller.dll and bin/pccontroller.h on Windows
(.so on Linux and .dylib on macOS). This requires CGO_ENABLED=1 and a C
compiler compatible with the target Go toolchain. On Windows the build script
checks candidates from PATH and the user-scoped package store against
go env GOARCH, their target triple, and their predefined macros. It rejects
Cygwin/MSYS compilers even when they are the first gcc.exe on PATH, selects
the newest native MinGW-w64/Windows-GNU candidate, and provisions the exact
latest-resolved package from the host-tool lock when none is installed. Set CC explicitly to a valid
native compiler when automatic selection is not appropriate, or pass
--no-compiler-bootstrap to prohibit installation. Standard HTTP proxy
environment variables are preserved for the package manager.
Every call returns allocated UTF-8 JSON. Copy or parse it, then call
PCControllerFree exactly once. The response envelope is:
{"ok":true,"handle":1,"result":{}}or:
{"ok":false,"error":"description"}Requests and representative operations:
{"operation":"create","options":{"vid":"1A86","pid":"7523","name":"USB-SERIAL CH340"}}
{"operation":"connect","handle":1,"timeout_ms":15000}
{"operation":"commands","handle":1}
{"operation":"execute","handle":1,"command":"status"}
{"operation":"status","handle":1}
{"operation":"temperatures","handle":1,"rescan":true}
{"operation":"event_next","handle":1,"after_id":42,"kind":"rf","timeout_ms":30000}
{"operation":"snapshot","handle":1}
{"operation":"rf_list","handle":1}
{"operation":"close","handle":1}
{"operation":"destroy","handle":1}
{"operation":"ports"}
{"operation":"host_create","host_options":{"data_root":"APPLICATION_DATA/PCController","app_id":"embedding-application","disable_auto_connect":false,"disable_native":false,"http_address":"127.0.0.1:8787"}}
{"operation":"host_start","handle":2}
{"operation":"host_call","handle":2,"method":"controller.capabilities","params":{}}
{"operation":"host_endpoints","handle":2}
{"operation":"host_stop","handle":2,"timeout_ms":10000}
{"operation":"host_destroy","handle":2,"timeout_ms":10000}host_create accepts config_path together with data_root only when it
names the exact data_root/config.json file. Conflicting paths are rejected,
so foreign consumers may state the canonical file explicitly without creating
two configuration roots.
| Operation | Behavior |
|---|---|
create |
Create a client from the full Go Options JSON shape and return a handle. |
ports |
Enumerate serial ports; no handle is required. |
connect |
Auto-detect, or explicitly open top-level port when provided; returns the snapshot. |
commands |
Return the shared command catalog with names, aliases, usage, summary, and task group. |
execute |
Run any registered shell command and return {"output":"..."}. This includes status/settings, display/strip, macros, streamed melodies/status effects, relay/PWM/RF, reset, raw query, and programming commands. |
status |
Request and decode current telemetry, including reset fields when supplied. |
temperatures |
Return named DS18B20 role/ROM/value records; rescan:true asks the board to rescan first. |
event_next |
Wait for the first retained/new event with ID greater than after_id; optional kind filters it. |
snapshot |
Return the latest connection, identity, status, and settings state without a UART request. |
rf_list |
Fetch every page of learned RF entries. |
close |
Close and pause automatic reconnect while keeping the handle valid. |
destroy |
Shut down the client and remove the handle. |
host_create |
Construct an idle embeddable Host with application-owned configuration, data, branding, native IPC, integration, and optional HTTP settings. |
host_start |
Claim ownership and start the canonical client, dispatcher, in-process RPC, native-local listener, and optional HTTP/WebSocket listener. |
host_call |
Invoke one canonical RPC method directly in process; method and params have the same meaning as native and network calls. |
host_endpoints |
Return only the native/TCP endpoints that successfully started. |
host_stop |
Cancel, drain, and stop a Host while retaining its handle. |
host_destroy |
Stop a Host if needed and remove its handle. |
timeout_ms defaults to 15000 for handle operations. For event_next, use a
larger timeout when an idle wait is expected. Event IDs are monotonic within
the client; pass the returned event's ID as the next after_id to avoid
receiving it again. kind uses the host event names such as key, door,
rf, macro, connect, or disconnect.
Handles are local to the process that loaded the library. Operations on one handle are serialized, so callers from multiple native threads cannot interleave native UART requests.
A direct create client handle is itself a serial owner; it does not silently
attach to another coordinator. Prefer a host_create handle for application
embedding: it owns one canonical Host, exposes the same dispatcher in process,
and can serve protected native-local IPC concurrently with optional
HTTP/WebSocket. If another process already owns the Host identity or listener,
the application must use that coordinator's advertised native endpoint or
:8787 rather than opening UART as an implicit fallback. CLI exec, batch,
monitor, reset, shell, and programming processes automatically route through
the standard primary process.
Host handles and direct client handles share one numeric namespace but are different lifecycle types. Pass each handle only to operations for the type that created it. Calls for one handle are serialized. Keep the shared library loaded until process shutdown so Go runtime threads are never unloaded early.
The complete Go and Pealayer lifecycle, cancellation, logging, endpoint, and fallback contract is in Embeddable host lifecycle.
Background melody play and rgb effect play operations remain active while
their handle and the hosting process remain alive. They can be canceled through
another execute request (melody stop or rgb effect stop). Definitions are
PC-side JSON configuration only; they are not mixed with the MCU's
EEPROM-owned settings.
The complete per-domain reachability contract is in the Control-Surface Capability Matrix.
examples/c_abi_smoke.c is a minimal Windows consumer that resolves both
exports, invokes the port-list operation, and releases the returned string.
Build it with any compatible MinGW-w64 compiler. Treat the Go shared runtime
as process-lifetime state: keep the DLL loaded and let normal process shutdown
unload it instead of calling FreeLibrary while Go runtime threads are active.
The same stable C exports are directly usable by desktop automation tools; no shell process, temporary file, or command-line quoting is required. An AutoHotkey v2 call is:
dll := A_ScriptDir "\pccontroller.dll"
json := '{"operation":"ports"}'
request := Buffer(StrPut(json, "UTF-8"))
StrPut(json, request, "UTF-8")
resultPtr := DllCall(dll "\PCControllerInvoke", "Ptr", request.Ptr, "CDecl Ptr")
try response := StrGet(resultPtr, "UTF-8")
finally DllCall(dll "\PCControllerFree", "Ptr", resultPtr, "CDecl")Office VBA can declare the same pointer ABI with PtrSafe/LongPtr, pass a
null-terminated UTF-8 byte buffer to PCControllerInvoke, copy the returned
UTF-8 JSON, and release it with PCControllerFree. Match the Office and DLL
architectures. This explicit process-lifetime ABI is the supported automation
surface; the portable package does not silently register a machine-wide COM
class or require administrator access.