...where the 'K' stands for quality. https://ktag.clubk.club/Technology/SystemK/
  • C 98.2%
  • Python 1.2%
  • CMake 0.6%
Find a file
Joe Kearney baded1dc61 SystemK v2.00: Interface Improvements (#15)
## 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
2026-03-26 02:08:40 +00:00
Audio SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
BLE SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
Events SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
Game SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
IR SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
Logging Annum Novum Faustum Felicem MMXXVI (#11) 2026-01-10 23:18:53 +00:00
Menu SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
NeoPixels SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
Protocols Annum Novum Faustum Felicem MMXXVI (#11) 2026-01-10 23:18:53 +00:00
Settings SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
States SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
.gitignore SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
CMakeLists.txt Added support for configurable logging on the ESP32 platform. (#10) 2026-01-10 22:25:50 +00:00
Colors.h Annum Novum Faustum Felicem MMXXVI (#11) 2026-01-10 23:18:53 +00:00
Console_HW_Interface.h SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
Developer Certificate of Origin.txt Initial public release of SystemK. 2025-01-25 13:45:14 -06:00
Kconfig Added support for configurable logging on the ESP32 platform. (#10) 2026-01-10 22:25:50 +00:00
KIsForQuality.png Reworked BLE according to v0.12 of the KTag Beacon Specification (#5) 2025-09-01 17:44:10 +00:00
LICENSE Annum Novum Faustum Felicem MMXXVI (#11) 2026-01-10 23:18:53 +00:00
README.md Reworked BLE according to v0.12 of the KTag Beacon Specification (#5) 2025-09-01 17:44:10 +00:00
Results.h Added Parse_KEvent_ID() (#14) 2026-02-28 14:44:23 +00:00
SystemK.c SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00
SystemK.h SystemK v2.00: Interface Improvements (#15) 2026-03-26 02:08:40 +00:00

SystemK

...where the 'K' stands for Quality.

K is for Quality

SystemK is the name for the common software that is at the core of KTag devices.

This is that software.