An independent, clean-room Swift implementation of the Philips Hue Entertainment API for Apple platforms.
HueEntertainmentKit provides high-performance, real-time spatial lighting synchronization with strict security guarantees:
- CLIP v2 HTTP: Native
URLSessionor high-throughput Swift-NIO HTTP/1.1 (NIOHueHTTPTransport) with strict TLS certificate chain validation against official Signify Hue root CAs. - HueStream v2 DTLS: UDP streaming powered by Apple Network framework via
NIOTransportServiceswith TLS 1.2 PSK encryption and Swift-NIO channel duplex pipelines (HueStreamChannelHandler). - Ecosystem Modernization: Built-in support for
swift-log,swift-collections(allocation-freeDequesliding windows), andswift-async-algorithms(_throttlesequence pacing). - Photosensitivity Safety: Built-in
HueSafeFrameLimiterenforcing Philips Hue guidance to keep rapid brightness transitions under 5 Hz. - Real-Time Audio: Lock-free, zero-allocation C11 atomic seqlock mailbox for high-frequency audio render callbacks and AUv3 plug-ins.
- Swift 6 Strict Concurrency: Data-race free, actor-isolated session lifecycle and
Sendabletypes throughout.
HueEntertainmentKit is modularized into focused Swift Package libraries:
| Product | Description |
|---|---|
HueEntertainmentKit |
Core discovery (Bonjour/Broker/Manual), pushlink authentication, CLIP v2 client, DTLS transport, packet encoder, and actor-owned session lifecycle. |
HueEntertainmentEffects |
Spatial coordinate effect primitives (area, radial, chases), animation timelines, source-over alpha blending, and photosensitivity safety limiting. |
HueEntertainmentAudio |
Lock-free audio feature mailbox (HueAudioFeatureMailbox) exposing RMS, peak, transient strength, and 8-band spectral energy. |
HueEntertainmentTesting |
In-memory mocks (MockHueHTTPTransport, MockHueDTLSTransport, fixtures) for hardware-free unit testing. |
hue-entertainment-diagnostics |
Read-only CLI executable for discovering bridges and inspecting entertainment configurations without pairing. |
- Swift: 6.4 or later
- Xcode: 27.0 or later
- Supported Platforms:
- macOS 15.0+ (Sequoia)
- iOS 18.0+
- tvOS 18.0+
- visionOS 2.0+
Add HueEntertainmentKit as a dependency in your Package.swift:
dependencies: [
.package(url: "https://github.com/adeze/HueEntertainmentKit.git", from: "0.2.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "HueEntertainmentKit", package: "HueEntertainmentKit"),
.product(name: "HueEntertainmentEffects", package: "HueEntertainmentKit"),
// Optional: for AUv3 / real-time audio analysis
.product(name: "HueEntertainmentAudio", package: "HueEntertainmentKit"),
]
)
]- In Xcode, select File > Add Package Dependencies...
- Enter the repository URL:
https://github.com/adeze/HueEntertainmentKit.git - Select the version rule (e.g. Up to Next Major
0.3.0) and choose the products needed for your target.
Build or preview interactive DocC documentation locally:
swift package generate-documentation --target HueEntertainmentKit
swift package --disable-sandbox preview-documentation --target HueEntertainmentKitRun strict linting with the bundled SwiftLint command plugin:
swift package --allow-writing-to-package-directory swiftlint lint Sources Tests --strictFind Hue bridges on the local network via reactive Bonjour mDNS streaming, one-shot discovery, cloud broker lookup, or manual host entry:
import HueEntertainmentKit
let discovery = HueBridgeDiscovery()
// Real-time Bonjour streaming as bridges resolve:
Task {
for await endpoint in discovery.bonjourStream() {
print("Discovered bridge at \(endpoint.host) (ID: \(endpoint.bridgeID ?? "unknown"))")
}
}
// Or one-shot discovery (scans for 3 seconds):
let bridges = try await discovery.discoverBonjour(for: .seconds(3))
// Or manual configuration if IP/Host is already known:
let endpoint = try discovery.manual(host: "192.168.1.50", bridgeID: "001788FFFE112233")Pairing requests user consent via the physical bridge link button. Store credentials safely in Keychain:
import HueEntertainmentKit
let transport = URLSessionHueHTTPTransport(
trustPolicy: .hueBridge(bridgeID: endpoint.bridgeID)
)
let client = HueBridgeClient(endpoint: endpoint, transport: transport)
// Poll for pushlink button press (with progress callback)
let result = try await client.pairWaitingForLinkButton(
deviceType: "MyEntertainmentApp#MacBook",
timeout: .seconds(30)
) { progress in
switch progress.state {
case .waitingForLinkButton:
print("Please press the link button on your Hue bridge...")
case .paired:
print("Bridge linked successfully!")
}
}
// Persist credentials in macOS/iOS Keychain:
let store = KeychainHueCredentialStore()
try store.save(result.credentials, alias: "living-room-bridge")guard let credentials = try store.load(alias: "living-room-bridge") else { return }
let authenticatedClient = HueBridgeClient(
endpoint: endpoint,
credentials: credentials,
transport: transport
)
let configs = try await authenticatedClient.entertainmentConfigurations()
for config in configs {
print("Configuration: \(config.name) (\(config.channels.count) channels)")
}HueEntertainmentSession manages the exclusive stream claim, DTLS handshake, 50 Hz frame pumping, and graceful cleanup. Observe state changes reactively via stateUpdates:
import HueEntertainmentKit
import HueEntertainmentEffects
guard let config = configs.first else { return }
let dtlsTransport = try NIOTSDTLSTransport()
let session = HueEntertainmentSession(
control: authenticatedClient,
transport: dtlsTransport,
frameRate: 50
)
// Observe session state transitions (.claiming, .handshaking, .streaming, .releasing, .idle):
Task {
for await state in session.stateUpdates {
print("Session state: \(state)")
}
}
// Acquire ownership and establish DTLS stream
try await session.start(
configuration: config,
endpoint: endpoint,
credentials: credentials
)
// Apply safety limiting to protect against rapid strobing
var limiter = HueSafeFrameLimiter()
for frameIndex in 0..<250 { // ~5 seconds at 50Hz
// Create colors via presets, hex, or CoreGraphics:
let redColor = HueRGBColor.red
let channel0 = HueChannelColor(channelID: 0, color: redColor)
let candidateFrame = try HueFrame(colors: [channel0])
// Respect photosensitivity limits (< 5 Hz transitions)
let safeFrame = try limiter.limit(candidateFrame, at: .milliseconds(frameIndex * 20))
try await session.submit(safeFrame)
try await Task.sleep(for: .milliseconds(20))
}
// Graceful release of bridge stream
try await session.stop()Convert seamlessly between CoreGraphics, SwiftUI, hex strings, and official Philips Hue CIE 1931 xy gamuts (Gamut A, B, and C):
import CoreGraphics
import HueEntertainmentKit
// 1. CoreGraphics and Hex initialization
let hexColor = try HueRGBColor(hex: "#FF5500")
let cgColor = hexColor.cgColor
// 2. Named presets
let preset = HueRGBColor.cyan
// 3. CIE 1931 xy conversion with Philips Hue Gamut C clamping:
let xyBrightness = preset.toXYBrightness(gamut: .gamutC)
print("CIE xy: (\(xyBrightness.x), \(xyBrightness.y)), brightness: \(xyBrightness.brightness)")
// 4. Convert back to RGB:
let rgbRestored = xyBrightness.toRGB(gamut: .gamutC)Publish features in non-allocating render threads and consume them safely in a 50 Hz control loop:
import HueEntertainmentAudio
let mailbox = HueAudioFeatureMailbox()
// Inside CoreAudio / AUv3 render thread (no locks, no heap allocations):
func processAudioRender(hostTime: UInt64, rms: Float, peak: Float) {
let features = HueAudioFeatures(
hostTime: hostTime,
rms: rms,
peak: peak,
spectralCentroid: 1200.0,
transientStrength: 0.5,
bandEnergy: (0.1, 0.2, 0.4, 0.8, 0.3, 0.2, 0.1, 0.05)
)
mailbox.publish(features)
}
// Inside your 50 Hz visualizer update loop:
if let latestSnapshot = mailbox.latest() {
// Map audio energy to light effects...
}Run the bundled diagnostic CLI without pairing or changing bridge state:
# Discover bridges on the local network
swift run hue-entertainment-diagnostics discover
# Inspect bridge configuration (reads credentials from Keychain)
swift run hue-entertainment-diagnostics inspect \
--host bridge.local \
--bridge-id 001788FFFE112233 \
--credential-alias living-room-bridge- Root CA Validation: The package embeds both official Signify Hue Bridge root certificate authorities and verifies the certificate common name matches the bridge ID. No self-signed overrides or insecure trust modes exist.
- Credential Hygiene: Credentials are stored in Keychain. Raw credentials cannot be passed via CLI arguments and are redacted from string representations (
HueCredentials.descriptionoutputsHueCredentials(redacted)). - Non-Takeover Guarantee: By default,
HueEntertainmentSessionrefuses to preempt an active stream owned by another application (.failIfActive). - Clean State Teardown: Closing a session guarantees that DTLS is shut down, the stream is released on the bridge, and configuration state is verified inactive.
For further details, see SECURITY.md and ARCHITECTURE.md.
- Architecture & Protocol Design
- Spatial Effect Design Guide
- Live Bridge Validation Protocol
- AUv3 Example Project
- Security Policy
- Contributing Guidelines
- HueEntertainmentKit is an independent clean-room implementation based solely on publicly available Philips Hue documentation and network protocol observations.
- No source code or binaries from the proprietary Philips Hue Entertainment Development Kit (EDK) are included, referenced, or redistributed.
- Philips Hue is a trademark of Signify Holding. This project is not affiliated with, endorsed by, or sponsored by Signify.
- See NOTICE for third-party design references.
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.