...where the 'K' stands for quality.
https://ktag.clubk.club/Technology/SystemK/
- C 98.2%
- Python 1.2%
- CMake 0.6%
## Overview
Several improvements were made to the SystemK Hardware Abstraction Layer (HAL) and
related interfaces. They are ordered from least to most invasive:
| # | Change | Risk |
|---|--------|------|
| 1.1 | Add `BLE_Init()` to the BLE HAL | Low |
| 1.2 | Add `SETTINGS_Load()` to the Settings HAL | Low |
| 1.3 | Document `BLE_Quiet` / `BLE_Unquiet` semantics | None |
| 2.1 | Fix `HW_Execute_Console_Command` parameter type | Low |
| 2.2 | Tighten `BLE_GetMyAddress` pointer parameter | Low |
| 2.3 | Add `max_len` parameter to `SETTINGS_get_device_name` | Low |
| 3.1 | Remove `HW_NeoPixels_Get_My_Color()` from the HAL | Medium |
| 3.2 | Replace `HW_NeoPixels_Set_RGB` with `HW_NeoPixels_Set_Color` | Medium |
| 4.1 | Replace `void *Data` in `AudioAction_T` with a typed union | High |
| 4.2 | Make `Prepare_Tag` / `Send_Tag` accept explicit arguments | Medium |
| 4.3 | Replace generic settings accessors with per-setting typed functions | High |
---
## Phase 1 — Pure Additions
### 1.1 `BLE_Init()` added to `BLE/BLE_HW_Interface.h`
BLE initialization previously happened implicitly inside each platform
implementation with no explicit contract. Platform porters had to reverse-engineer
where and whether initialization was expected.
```c
// New declaration:
SystemKResult_T BLE_Init(void);
```
`SystemK.c` now calls `BLE_Init()` from `Initialize_SystemK()`, before the state
machine task is created.
---
### 1.2 `SETTINGS_Load()` added to `Settings/Settings_Interface.h`
Settings loading also happened implicitly. `SETTINGS_Save()` existed in the
interface but its counterpart did not.
```c
// New declaration:
SystemKResult_T SETTINGS_Load(void);
```
`SystemK.c` now calls `SETTINGS_Load()` from `Initialize_SystemK()`, before
`BLE_Init()` and the state machine task.
---
### 1.3 `BLE_Quiet` / `BLE_Unquiet` semantics documented
A comment block was added to both declarations in `BLE/BLE_HW_Interface.h`
describing the full contract: timer behavior, idempotency guarantees, interaction
between timed and indefinite quiet, and the `SYSTEMK_RESULT_NOT_READY` return
condition. No code changes.
---
## Phase 2 — Mechanical Type Fixes
### 2.1 `HW_Execute_Console_Command` parameter type corrected
The command is a text string but was typed as `const uint8_t *const`. Callers
worked around this with explicit casts.
```c
// Before:
SystemKResult_T HW_Execute_Console_Command(const uint8_t * const command);
// After:
SystemKResult_T HW_Execute_Console_Command(const char * const command);
```
---
### 2.2 `BLE_GetMyAddress` pointer parameter tightened
The raw `uint8_t *` parameter gave no indication of the required buffer size.
```c
// Before:
SystemKResult_T BLE_GetMyAddress(uint8_t * BD_ADDR);
// After:
SystemKResult_T BLE_GetMyAddress(uint8_t BD_ADDR[BD_ADDR_SIZE]);
```
No change is required at call sites; the array type is compatible.
---
### 2.3 `SETTINGS_get_device_name` gains a `max_len` parameter
Callers had to know the correct buffer size out-of-band.
```c
// Before:
SystemKResult_T SETTINGS_get_device_name(char* name);
// After:
SystemKResult_T SETTINGS_get_device_name(char* name, size_t max_len);
```
---
## Phase 3 — NeoPixel Refactor
### 3.1 `HW_NeoPixels_Get_My_Color()` removed from the HAL
Team color is a game/settings concept, not a hardware concept. The HAL should not
need to know about team assignments.
```c
// Removed:
color_t HW_NeoPixels_Get_My_Color(void);
```
The seven call sites in `NeoPixels/Animations/` now use the `GAME_Get_My_Color()`
inline helper in `Game/Game.h`, which reads team, player, and weapon from settings
and derives the color via `PROTOCOLS_GetColor()`.
---
### 3.2 `HW_NeoPixels_Set_RGB` replaced by `HW_NeoPixels_Set_Color`
The old signature required callers to decompose `color_t` into separate R, G, B
components and apply gamma correction themselves. Some call sites also swapped
channels to compensate for GRB-order hardware — hardware-specific behavior that
belongs in the HAL.
```c
// Before:
SystemKResult_T HW_NeoPixels_Set_RGB(NeoPixelsChannel_T channel, uint8_t position,
uint8_t red, uint8_t green, uint8_t blue);
// After:
SystemKResult_T HW_NeoPixels_Set_Color(NeoPixelsChannel_T channel, uint8_t position,
color_t color);
```
The platform implementation is now responsible for gamma correction and any
color-channel reordering required by its hardware.
---
## Phase 4 — Structural Changes
### 4.1 `AudioAction_T.Data` changed from `void *` to a typed union
`void *Data` was an untyped payload with no documented contract per action ID.
In practice only two action IDs used non-null data, both passing a pointer to a
`uint8_t`. This change makes the type explicit and eliminates pointer indirection.
```c
// Before:
typedef struct {
AudioActionID_T ID;
bool Play_To_Completion;
void * Data;
} AudioAction_T;
// After:
typedef union {
uint8_t number; // used by AUDIO_PRONOUNCE_NUMBER_0_TO_100 and AUDIO_SET_VOLUME
} AudioActionData_T;
typedef struct {
AudioActionID_T ID;
bool Play_To_Completion;
AudioActionData_T Data;
} AudioAction_T;
```
Call sites that previously passed `(void *)0x00` now omit `Data` entirely (C99
zero-initializes unspecified members). Call sites that previously passed
`(void *)&variable` now pass `{ .number = variable }` by value.
The struct shrinks from 12 to 8 bytes. FreeRTOS queues created with
`xQueueCreate(n, sizeof(AudioAction_T))` pick up the new size automatically at
runtime.
---
### 4.2 `Prepare_Tag` / `Send_Tag` accept explicit arguments
The original interface communicated through hidden global state: `Prepare_Tag()`
encoded a packet from settings into a module-level variable; `Send_Tag()` consumed
it. The contract was invisible to callers and to platform implementers.
The new interface uses a handle:
```c
// Before:
SystemKResult_T Prepare_Tag(void);
SystemKResult_T Send_Tag(void);
// After:
PreparedTag_T *Prepare_Tag(const TagPacket_T *packet);
SystemKResult_T Send_Tag(PreparedTag_T *tag);
```
`PreparedTag_T` is defined in `Game/Game.h` (SystemK code) and contains a
`TimedPulseTrain_T *pulse_train` member.
SystemK builds the `TagPacket_T` from current settings via
`GAME_Build_My_Tag_Packet()` in `Game/Game.h` and stores the returned
`PreparedTag_T *` in `GAME_Prepared_Tag` (defined in `Game/Game.c`,
declared `extern` in `Game/Game.h`) so it is accessible from both the countdown
state (which calls `Prepare_Tag`) and the interacting substate (which calls
`Send_Tag`).
The platform is responsible for encoding the pulse train in `Prepare_Tag` and
transmitting it in `Send_Tag`. `PROTOCOLS_EncodePacket()` is called exactly once,
in `Prepare_Tag`.
---
### 4.3 Generic settings accessors replaced with per-setting typed functions
`SETTINGS_get_uint8_t(id, ptr)` and `SETTINGS_get_uint32_t(id, ptr)` baked the
storage type of each setting into every call site via the function name. If a
setting's range ever grows (e.g., `Max_Health` exceeding 255), all call sites
silently truncate with no compile-time warning.
The `SystemKSettingID_T` enum is removed from the public interface entirely. In
its place, each setting has one dedicated typed getter and one typed setter:
```c
// Removed from the interface:
SystemKResult_T SETTINGS_get_uint8_t(SystemKSettingID_T id, uint8_t *value);
SystemKResult_T SETTINGS_set_uint8_t(SystemKSettingID_T id, uint8_t value);
SystemKResult_T SETTINGS_get_uint32_t(SystemKSettingID_T id, uint32_t *value);
SystemKResult_T SETTINGS_set_uint32_t(SystemKSettingID_T id, uint32_t value);
// Added (one pair per setting, derived from Settings.csv):
SystemKResult_T SETTINGS_get_Is_Right_Handed(uint8_t *value);
SystemKResult_T SETTINGS_set_Is_Right_Handed(uint8_t value);
SystemKResult_T SETTINGS_get_Audio_Volume(uint8_t *value);
SystemKResult_T SETTINGS_set_Audio_Volume(uint8_t value);
SystemKResult_T SETTINGS_get_Team_ID(uint8_t *value);
SystemKResult_T SETTINGS_set_Team_ID(uint8_t value);
SystemKResult_T SETTINGS_get_Player_ID(uint8_t *value);
SystemKResult_T SETTINGS_set_Player_ID(uint8_t value);
SystemKResult_T SETTINGS_get_Weapon_ID(uint8_t *value);
SystemKResult_T SETTINGS_set_Weapon_ID(uint8_t value);
SystemKResult_T SETTINGS_get_Max_Health(uint8_t *value);
SystemKResult_T SETTINGS_set_Max_Health(uint8_t value);
SystemKResult_T SETTINGS_get_N_Special_Weapons_On_Reentry(uint8_t *value);
SystemKResult_T SETTINGS_set_N_Special_Weapons_On_Reentry(uint8_t value);
SystemKResult_T SETTINGS_get_T_Start_Game_in_ms(uint32_t *value);
SystemKResult_T SETTINGS_set_T_Start_Game_in_ms(uint32_t value);
SystemKResult_T SETTINGS_get_T_Game_Length_in_ms(uint32_t *value);
SystemKResult_T SETTINGS_set_T_Game_Length_in_ms(uint32_t value);
SystemKResult_T SETTINGS_get_Secondary_Color(uint32_t *value);
SystemKResult_T SETTINGS_set_Secondary_Color(uint32_t value);
// Read-only platform constant (not in Settings.csv):
SystemKResult_T SETTINGS_get_Device_Type(uint32_t *value);
```
The function names are derived from the `Key` column in `Settings/Settings.csv`.
`Settings/generate_settings_code.py` has been updated to generate both the
interface declarations and the per-setting implementations. Adding a new setting
now requires only a new row in the CSV; changing a setting's type requires editing
one CSV cell, regenerating, and fixing the compile errors the compiler identifies.
---
## Platform Porting Guide
This section lists every change a platform implementation must make to compile
against the updated SystemK interface.
### Settings (`Settings_Interface.h`)
1. **Add `SETTINGS_Load()`.**
Rename your existing initialization function (e.g., `Initialize_Settings()`) to
`SETTINGS_Load()`. It must return `SystemKResult_T` and signal readiness by
setting the `SYS_SETTINGS_READY` event group bit before returning.
2. **Replace the four generic accessors with per-setting functions.**
Delete `SETTINGS_get_uint8_t`, `SETTINGS_set_uint8_t`, `SETTINGS_get_uint32_t`,
and `SETTINGS_set_uint32_t`. Implement one getter and one setter for each
setting listed in `Settings/Settings.csv`, using the naming convention
`SETTINGS_get_<Key>` / `SETTINGS_set_<Key>`. Run
`Settings/generate_settings_code.py` to generate a starting implementation.
3. **Add `SETTINGS_get_Device_Type(uint32_t *value)`.**
This is a read-only function that returns your platform's BLE device type
constant. It is not generated from `Settings.csv`.
4. **Update `SETTINGS_get_device_name`.**
Add a `size_t max_len` parameter and use it to limit the copy into the caller's
buffer, ensuring null-termination within `max_len` bytes.
### BLE (`BLE/BLE_HW_Interface.h`)
5. **Add `BLE_Init()`.**
Implement `SystemKResult_T BLE_Init(void)`. Move any BLE host/controller
initialization that previously happened at startup into this function. Return
`SYSTEMK_RESULT_SUCCESS` on success or `SYSTEMK_RESULT_UNSPECIFIED_FAILURE` if
the BLE stack fails to initialize.
6. **Update `BLE_GetMyAddress`.**
Change the parameter from `uint8_t *BD_ADDR` to `uint8_t BD_ADDR[BD_ADDR_SIZE]`.
The function body requires no change; only the signature needs updating.
### Console (`Console_HW_Interface.h`)
7. **Update `HW_Execute_Console_Command`.**
Change the parameter from `const uint8_t *const command` to
`const char *const command`. Remove any `(const char *)` casts at call sites
within your platform code.
### NeoPixels (`NeoPixels/NeoPixel_HW_Interface.h`)
8. **Delete `HW_NeoPixels_Get_My_Color()`.**
Remove the implementation entirely. Team color is now resolved in SystemK.
9. **Replace `HW_NeoPixels_Set_RGB` with `HW_NeoPixels_Set_Color`.**
```c
// Before:
SystemKResult_T HW_NeoPixels_Set_RGB(NeoPixelsChannel_T channel,
uint8_t position,
uint8_t red, uint8_t green, uint8_t blue);
// After:
SystemKResult_T HW_NeoPixels_Set_Color(NeoPixelsChannel_T channel,
uint8_t position,
color_t color);
```
Your implementation must now decompose `color` using `Red()`, `Green()`,
`Blue()` and apply gamma correction. If your hardware uses GRB channel ordering,
swap the red and green components here. Example for RGB-order hardware with
gamma correction:
```c
SystemKResult_T HW_NeoPixels_Set_Color(NeoPixelsChannel_T channel,
uint8_t position,
color_t color)
{
// Apply gamma correction and write to hardware:
set_pixel(channel, position,
Gamma8[Red(color)],
Gamma8[Green(color)],
Gamma8[Blue(color)]);
return SYSTEMK_RESULT_SUCCESS;
}
```
### Audio (`Audio/Audio_HW_Interface.h`)
10. **Update `AudioAction_T` data access.**
`action.Data` is no longer a `void *`. Where your implementation previously
dereferenced the pointer — e.g., `*((uint8_t *)action.Data)` — use the union
member directly:
```c
// Before:
uint8_t number = *((uint8_t *)action.Data);
// After:
uint8_t number = action.Data.number;
```
### IR (`IR/IR_HW_Interface.h`)
11. **Implement the new `Prepare_Tag` / `Send_Tag` signatures.**
```c
PreparedTag_T *Prepare_Tag(const TagPacket_T *packet);
SystemKResult_T Send_Tag(PreparedTag_T *tag);
```
`PreparedTag_T` is defined in `Game/Game.h` and contains a single member,
`TimedPulseTrain_T *pulse_train`. You do not need to define the struct yourself.
`Prepare_Tag` receives a fully populated `TagPacket_T` from SystemK. It
must encode the packet via `PROTOCOLS_EncodePacket()`, store the result in a
static `PreparedTag_T`, prepare the hardware for transmission, and return a
pointer to that static. **Do not call `PROTOCOLS_EncodePacket()` in `Send_Tag`**
— encoding must happen in `Prepare_Tag` to minimize transmit latency.
`Send_Tag` receives the pointer returned by `Prepare_Tag` and transmits the
pre-encoded pulse train via `tag->pulse_train`. It must not re-encode anything.
Your platform no longer needs to read settings (team ID, player ID, weapon ID)
in either function; SystemK provides a fully populated `TagPacket_T`.
Example:
```c
static PreparedTag_T Prepared_Shot;
PreparedTag_T *Prepare_Tag(const TagPacket_T *packet)
{
Prepared_Shot.pulse_train = PROTOCOLS_EncodePacket((TagPacket_T *)packet);
// ... log, enable transmitter hardware ...
return &Prepared_Shot;
}
SystemKResult_T Send_Tag(PreparedTag_T *tag)
{
// ... transmit tag->pulse_train ...
return SYSTEMK_RESULT_SUCCESS;
}
```
Co-authored-by: Joe Kearney <joe@clubk.club>
Reviewed-on: #15
|
||
|---|---|---|
| Audio | ||
| BLE | ||
| Events | ||
| Game | ||
| IR | ||
| Logging | ||
| Menu | ||
| NeoPixels | ||
| Protocols | ||
| Settings | ||
| States | ||
| .gitignore | ||
| CMakeLists.txt | ||
| Colors.h | ||
| Console_HW_Interface.h | ||
| Developer Certificate of Origin.txt | ||
| Kconfig | ||
| KIsForQuality.png | ||
| LICENSE | ||
| README.md | ||
| Results.h | ||
| SystemK.c | ||
| SystemK.h | ||
