| name | convert-to-hal-rtapi |
| description | Convert a synchronous PoKeysLib subsystem into a non-blocking, LinuxCNC RT-capable async HAL implementation. Use when creating or modifying PoKeysLib*Async.c subsystem implementations, adding HAL pin exports, or verifying PoKeys protocol compliance for async conversions.
|
Convert PoKeysLib Subsystem to RT-Capable Async HAL Implementation
Description
This skill guides the conversion of synchronous (blocking) PoKeysLib subsystem implementations to asynchronous, real-time capable HAL implementations for LinuxCNC. The conversion splits blocking I/O operations into non-blocking request-sending functions and response-parsing callbacks, ensuring the RT thread never blocks.
When to Use This Skill
Use this skill when you need to:
- Convert an existing
PoKeysLib<Subsystem>.c file to async operation
- Add a new subsystem with real-time constraints to the PoKeys HAL driver
- Implement LinuxCNC HAL pin exports for a PoKeys subsystem
- Verify protocol compliance for PoKeys command implementations
Prerequisites
Required Knowledge
- C programming (C99 standard)
- LinuxCNC HAL architecture and pin concepts
- Real-time programming constraints (no blocking, no dynamic allocation)
- PoKeys USB/Ethernet device protocol
Required Files
- Original synchronous implementation:
PoKeysLib<Subsystem>.c
- Protocol specification:
PoKeys - protocol specification.pdf
- Header files:
PoKeysLibHal.h, PoKeysLibAsync.h
Required Tools
- Markitdown MCP server (for reading protocol specification PDF)
- LinuxCNC development environment
- HAL compiler (
halcompile)
Inputs
| Input | Type | Description | Required |
|---|
subsystem_name | string | Name of subsystem (e.g., "Encoders", "IO", "PEv2") | Yes |
source_file | path | Path to original PoKeysLib<Subsystem>.c file | Yes |
protocol_spec | path | Path to PoKeys - protocol specification.pdf | Yes |
device_structure | object | Pointer to sPoKeysDevice structure in PoKeysLibHal.h | Yes |
Outputs
| Output | Type | Description |
|---|
async_implementation | file | New PoKeysLib<Subsystem>Async.c file |
hal_export_function | function | export_<subsystem>_pins() function for HAL integration |
async_request_functions | functions | Non-blocking request functions (suffix: Async) |
parse_callbacks | functions | Response parsing callbacks (suffix: Parse) |
protocol_constants | definitions | Command codes and bit masks in PoKeysLibAsync.h |
Step-by-Step Instructions
Step 1: Verify Protocol Specification
Before writing any code, verify the subsystem's protocol specification:
1. Use Markitdown MCP server to read the protocol PDF:
- Tool: `mcp_microsoft_mar_convert_to_markdown`
- File: `PoKeys - protocol specification.pdf`
2. Document for your subsystem:
- Command codes (main and subcommands)
- Request structure (parameter bytes, payload bytes)
- Response structure (byte layout, data types)
- Bitfield definitions (options, status flags)
- Error codes and handling
3. Add missing definitions to `PoKeysLibAsync.h`:
- Command codes in `pokeys_command_t` enum
- Subcommands in subsystem-specific enums
- Bit mask constants (#define)
- Response offset constants
Example:
typedef enum {
PK_CMD_ENCODER_SETTINGS_GET = 0x16,
PK_CMD_ENCODER_KEYMAP_A_GET = 0x17,
} pokeys_command_t;
#define ENCODER_OPT_ENABLE (1 << 0)
#define ENCODER_OPT_X4_SAMPLING (1 << 1)
Step 2: Create Async Implementation File
Create PoKeysLib<Subsystem>Async.c:
#include "PoKeysLibHal.h"
#include "PoKeysLibAsync.h"
Step 3: Implement HAL Pin Export Function
Every subsystem MUST export HAL pins using this pattern:
int export_<subsystem>_pins(const char *prefix, long comp_id, sPoKeysDevice *device)
{
int r = 0;
rtapi_print_msg(RTAPI_MSG_ERR, "PoKeys: %s:%s: %s.<pin-name>\n",
__FILE__, __FUNCTION__, prefix);
r = hal_pin_<type>_newf(HAL_<direction>, &(target_pointer), comp_id,
"%s.<pin-name>", prefix);
if (r != 0) {
rtapi_print_msg(RTAPI_MSG_ERR, "PoKeys: %s:%s: %s.<pin-name> failed\n",
__FILE__, __FUNCTION__, prefix);
return r;
}
return 0;
}
Pin Naming Conventions:
- Digital input:
%s.<subsystem>.digin.<name>.in
- Digital output:
%s.<subsystem>.digout.<name>.out
- Indexed channels:
%s.<subsystem>.<ch>.<name>
Step 4: Convert Blocking Functions to Async
For each function in the original implementation:
Original (Blocking):
int32_t PK_SubsystemActionGet(sPoKeysDevice* device)
{
CreateRequest(device->request, 0xC4, 0, 0, 0, 0);
if (SendRequest(device) == PK_OK) {
device->field = device->response[8];
}
return PK_OK;
}
Converted (Async):
int PK_SubsystemActionGetAsync(sPoKeysDevice* device)
{
if (device == NULL) return PK_ERR_NOT_CONNECTED;
CreateAndSendRequestAsync(device, PK_CMD_SUBSYSTEM_ACTION_GET,
NULL, 0, NULL, 0, PK_SubsystemActionParse);
return PK_OK;
}
int PK_SubsystemActionParse(sPoKeysDevice* device, const uint8_t* response)
{
if (device == NULL || response == NULL) return PK_ERR_TRANSFER;
device->field = response[PK_RESPONSE_PAYLOAD_START];
return PK_OK;
}
Step 5: Handle Multi-Step Operations
For operations requiring multiple sequential requests:
typedef enum {
PK_SUBSYSTEM_STEP_NONE,
PK_SUBSYSTEM_STEP_GET_CONFIG,
PK_SUBSYSTEM_STEP_GET_STATUS,
PK_SUBSYSTEM_STEP_COMPLETE
} PKSubsystemStep;
typedef struct {
PKSubsystemStep step;
uint8_t request_id;
} PKSubsystemAsyncContext;
Step 6: Add Function Declarations to Header
Add to PoKeysLibAsync.h:
int export_<subsystem>_pins(const char *prefix, long comp_id, sPoKeysDevice *device);
int PK_<Subsystem><Action>Async(sPoKeysDevice* device);
int PK_<Subsystem><Action>Parse(sPoKeysDevice* device, const uint8_t* response);
Step 7: Testing
Test in this order:
-
Userspace HAL component (non-RT testing):
halrun <<EOF
loadusr -W pokeys_async
show pin
show param
exit
EOF
-
Real-time HAL component:
halrun <<EOF
loadrt threads name1=test-thread period1=1000000
loadrt pokeys_async
addf pokeys-async.0 test-thread
start
show pin
exit
EOF
-
Timing verification (oscilloscope + GPIO instrumentation)
Real-Time Constraints
CRITICAL - Always Follow These Rules:
Allowed in RT Thread
✅ Stack allocation (local variables)
✅ Direct memory access to pre-allocated structures
✅ Simple arithmetic and bitwise operations
✅ Conditional logic with bounded execution
✅ rtapi_print_msg() for debugging (sparingly)
Forbidden in RT Thread
❌ Blocking calls (SendRequest(), sleep(), usleep())
❌ Dynamic memory allocation (malloc, free)
❌ Unbounded loops (use fixed iteration counts)
❌ Floating-point in critical paths
❌ File operations
❌ Network waiting
❌ Mutex locks that might block
Quality Checklist
Before submitting your async implementation:
Examples
Complete Example: Encoder Subsystem
See the full example in tasks document, sections:
- Step 2: HAL Pin Export (lines 48-280)
- Step 2.5: Protocol Verification (lines 282-478)
- Step 3: Async Conversion (lines 480-610)
Quick Reference: Function Naming
| Original | Async Request | Parse Callback |
|---|
PK_EncoderConfigurationGet() | PK_EncoderConfigurationGetAsync() | PK_EncoderOptionsParse()
PK_FastEncodersOptionsParse() |
PK_EncoderValuesGet() | PK_EncoderValuesGetAsync() | PK_EncoderValuesGetAsync_ProcessPage0() |
References
- Detailed Guide: tasks - Complete step-by-step conversion guide
- Protocol Specification:
PoKeys - protocol specification.pdf
- Architecture Rules:
.github/instructions/pokeyshal-architecture.instructions.md
- HAL Documentation: LinuxCNC HAL documentation
Common Pitfalls
-
Using magic numbers instead of named constants
- ❌
CreateAndSendRequestAsync(device, 0xC4, ...)
- ✅
CreateAndSendRequestAsync(device, PK_CMD_ENCODER_SETTINGS_GET, ...)
-
Missing error handling in pin export
- ❌
hal_pin_u32_newf(...); // No error check
- ✅
r = hal_pin_u32_newf(...); if (r != 0) return r;
-
Blocking in parse callbacks
- ❌
SendRequest() in parse callback (BLOCKS!)
- ✅ Only extract data, no I/O operations
-
Inconsistent pin naming
- ❌
pokeys.encoder_3_count
- ✅
pokeys.encoder.3.count
-
Not verifying protocol specification
- ❌ Guessing byte offsets and bit masks
- ✅ Reading protocol PDF and documenting findings
Support
For questions or issues:
- Review the tasks document
- Check the protocol specification
- Examine existing async implementations (e.g.,
PoKeysLibEncodersAsync.c)
- Verify against architecture rules in
.github/instructions/
Version History
- v1.0 (2026-03-02) - Initial skill creation with protocol verification and HAL pin export patterns