Hardware integration
This guide explains how to integrate the Helm Protocol (SLP) into hardware devices such as TimeKeeper, CueKeeper, and CueMaster.
Table of Contents
Section titled “Table of Contents”- Architecture Overview
- Hardware Requirements
- SDK Integration
- Timecode Implementation
- Display Integration
- Network Configuration
- Testing
- Troubleshooting
Architecture Overview
Section titled “Architecture Overview”Device Roles
Section titled “Device Roles”| Device | Role | Primary Function |
|---|---|---|
| TimeKeeper | Client | Timecode display/generation |
| CueKeeper | Client | Cue list display |
| CueMaster | Master/Client | Standalone host or client |
Communication Flow
Section titled “Communication Flow” ┌─────────────────┐ │ Session Master │ │ (Cues/CueMaster)│ └────────┬────────┘ │ ┌──────────────┼──────────────┐ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │TimeKeeper│ │CueKeeper │ │CueKeeper │ └──────────┘ └──────────┘ └──────────┘Hardware Requirements
Section titled “Hardware Requirements”Minimum Specifications
Section titled “Minimum Specifications”| Component | Requirement |
|---|---|
| CPU | ARM Cortex-A53 or better |
| RAM | 512MB minimum, 1GB recommended |
| Storage | 4GB for OS and SDK |
| Network | 100Mbps Ethernet, WiFi optional |
| OS | Linux (Raspberry Pi OS, Buildroot) |
Recommended Platform
Section titled “Recommended Platform”Raspberry Pi Compute Module 4 with:
- Custom carrier board
- Industrial-grade eMMC
- Gigabit Ethernet
- USB-C power
Timecode I/O Hardware
Section titled “Timecode I/O Hardware”For TimeKeeper/CueMaster:
| Interface | Chip | Purpose |
|---|---|---|
| Audio ADC | PCM1808 | LTC input |
| Audio DAC | PCM5102 | LTC output |
| MIDI | UART + optoisolator | MTC I/O |
SDK Integration
Section titled “SDK Integration”Installation
Section titled “Installation”Rust (Recommended)
Section titled “Rust (Recommended)”Add to your Cargo.toml:
[dependencies]slp-client = { path = "../helm-sdk/slp-client" }slp-core = { path = "../helm-sdk/slp-core" }tokio = { version = "1", features = ["full"] }-
Build the FFI library:
Terminal window cd helm-sdkcargo build --release -p slp-ffi -
Link against
libslp_ffi.so(or.afor static) -
Include the header:
#include "helm-slp.h"
Basic Rust Example
Section titled “Basic Rust Example”use slp_client::{SlpClient, ClientConfig};use slp_core::{DeviceType, Role, TimecodePacket};
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { // Configure device let config = ClientConfig { device_name: "Studio TimeKeeper".to_string(), device_type: DeviceType::TimeKeeper, requested_role: Role::Hardware, version: env!("CARGO_PKG_VERSION").to_string(), };
// Create client let client = SlpClient::new(config)?;
// Start discovery client.start_discovery().await?;
// Wait for sessions tokio::time::sleep(std::time::Duration::from_secs(2)).await;
// List discovered sessions for session in client.discovered_sessions().await { println!("Found: {} at {}", session.name, session.address); }
// Connect to first session if let Some(session) = client.discovered_sessions().await.first() { client.connect(&session.address).await?;
// Listen for timecode let mut tc_rx = client.subscribe_timecode(); while let Ok(packet) = tc_rx.recv().await { update_display(&packet); } }
Ok(())}
fn update_display(packet: &TimecodePacket) { println!("TC: {}", packet.to_timecode_string());}Basic C Example
Section titled “Basic C Example”#include "helm-slp.h"#include <stdio.h>
void on_timecode(const slp_timecode_packet_t* packet, void* userdata) { char buffer[16]; slp_timecode_to_string(packet, buffer, sizeof(buffer)); printf("TC: %s\n", buffer);}
int main() { SlpClientHandle* client = NULL;
// Create client slp_result_t result = slp_client_create( "Studio TimeKeeper", SLP_DEVICE_TYPE_TIME_KEEPER, &client );
if (result != SLP_RESULT_OK) { fprintf(stderr, "Failed to create client\n"); return 1; }
// Start discovery slp_start_discovery(client);
// Connect (example address) result = slp_connect(client, "192.168.1.100:5353"); if (result != SLP_RESULT_OK) { fprintf(stderr, "Failed to connect\n"); slp_client_destroy(client); return 1; }
// Main loop while (slp_is_connected(client)) { // Process events... sleep(1); }
slp_client_destroy(client); return 0;}Timecode Implementation
Section titled “Timecode Implementation”Receiving Timecode
Section titled “Receiving Timecode”Timecode packets arrive via QUIC datagrams at the frame rate (e.g., 30Hz for 30fps).
Key Fields:
position_frames: Absolute frame count from 00:00:00:00frame_rate: Encoded frame rate (see spec)state: Playback state (Stopped, Playing, Paused, etc.)
Converting to Display Format:
fn frames_to_timecode(frames: u64, fps: u8) -> String { let fps = fps as u64; let hours = frames / (fps * 3600); let remaining = frames % (fps * 3600); let minutes = remaining / (fps * 60); let remaining = remaining % (fps * 60); let seconds = remaining / fps; let frame = remaining % fps;
format!("{:02}:{:02}:{:02}:{:02}", hours, minutes, seconds, frame)}Generating Timecode (TimeKeeper)
Section titled “Generating Timecode (TimeKeeper)”For devices that can inject timecode:
// From LTC decoderfn on_ltc_frame(hours: u8, mins: u8, secs: u8, frames: u8, fps: u8) { let packet = TimecodePacket::from_timecode(hours, mins, secs, frames, fps); // Modify source to indicate LTC input let packet = TimecodePacket { source: TimecodeSource::LtcInput, ..packet };
client.send_timecode(packet).await?;}LTC Audio Processing
Section titled “LTC Audio Processing”For reading LTC from audio input:
// Use a library like `ltc` crateuse ltc::LTCDecoder;
fn process_audio_buffer(buffer: &[i16], sample_rate: u32) { let mut decoder = LTCDecoder::new(sample_rate);
for frame in decoder.decode(buffer) { let tc = frame.timecode(); // Convert and send }}Display Integration
Section titled “Display Integration”CueKeeper Display Modes
Section titled “CueKeeper Display Modes”The CueKeeper supports multiple display modes:
1. Cue List Mode
Section titled “1. Cue List Mode”Shows upcoming and previous cues with current highlighted.
┌─────────────────────────────────┐│ ← PREV: Light cue 5 │├─────────────────────────────────┤│ ▶ CURRENT: Light cue 6 ││ 01:23:45:15 │├─────────────────────────────────┤│ → NEXT: Light cue 7 ││ in 00:00:15:00 │├─────────────────────────────────┤│ Light cue 8 ││ Light cue 9 │└─────────────────────────────────┘2. Countdown Mode
Section titled “2. Countdown Mode”Large countdown to next cue.
┌─────────────────────────────────┐│ ││ ▶ Light Cue 6 ││ ││ 00:15:22 ││ ││ NEXT: Light Cue 7 ││ │└─────────────────────────────────┘3. Timecode Only
Section titled “3. Timecode Only”Shows current timecode in large format.
┌─────────────────────────────────┐│ ││ ││ 01:23:45:15 ││ ││ ▶ PLAYING ││ │└─────────────────────────────────┘Display Update Recommendations
Section titled “Display Update Recommendations”| Element | Update Rate | Notes |
|---|---|---|
| Timecode | 30Hz | Match frame rate |
| Countdown | 10Hz | Smooth animation |
| Cue list | On change | Event-driven |
| Status | 1Hz | Battery, network, etc. |
Network Configuration
Section titled “Network Configuration”Default Ports
Section titled “Default Ports”| Port | Protocol | Purpose |
|---|---|---|
| 5353 | QUIC/UDP | SLP connections |
| 5354 | UDP | Discovery broadcast |
Firewall Rules
Section titled “Firewall Rules”For Linux/iptables:
# Allow SLP connectionsiptables -A INPUT -p udp --dport 5353 -j ACCEPTiptables -A INPUT -p udp --dport 5354 -j ACCEPT
# Allow mDNSiptables -A INPUT -p udp --dport 5353 -d 224.0.0.251 -j ACCEPTStatic IP Recommendation
Section titled “Static IP Recommendation”For production use, devices should use static IPs:
/etc/dhcpcd.conf:interface eth0static ip_address=192.168.1.100/24static routers=192.168.1.1static domain_name_servers=192.168.1.1Testing
Section titled “Testing”Unit Tests
Section titled “Unit Tests”Run SDK tests:
cd helm-sdkcargo testIntegration Tests
Section titled “Integration Tests”- Discovery Test
- Start Helm Cues as master
- Verify device discovers session
- Verify mDNS and UDP broadcast both work
- Connection Test
- Connect to session
- Verify token received
- Verify show sync received
- Timecode Test
- Start playback on master
- Measure latency on device
- Verify <1ms end-to-end
- Stress Test
- Connect 50+ devices
- Run for 24 hours
- Monitor memory, CPU, packet loss
Test Tools
Section titled “Test Tools”# Monitor network traffictcpdump -i eth0 port 5353 or port 5354
# Test discoveryavahi-browse -a
# Measure latencyping <master-ip>Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Device Not Discovered
Section titled “Device Not Discovered”- Check mDNS is working:
avahi-browse -a - Check UDP broadcast enabled
- Verify same subnet
- Check firewall rules
High Timecode Latency
Section titled “High Timecode Latency”- Check network congestion
- Disable WiFi, use Ethernet
- Check CPU usage (should be <50%)
- Verify QoS not throttling UDP
Connection Drops
Section titled “Connection Drops”- Check heartbeat interval (5 seconds)
- Verify network stability
- Check master logs for errors
- Verify session token valid
Debug Logging
Section titled “Debug Logging”Enable verbose logging:
tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .init();Support
Section titled “Support”For hardware integration support:
- GitHub Issues: https://github.com/helm/helm-sdk
- Email: hardware@helm.io