padl/swiftoca
SwiftOCA
Features
Controller (SwiftOCA)
- Device discovery:
OcaConnectionBrokerdiscovers AES70 devices via DNS-SD/Bonjour (usingNetServiceBrowseron Apple platforms, orlibdns_sdon Linux), with support for TCP, UDP, and WebSocket service types. Devices can also be registered manually for direct connection without DNS-SD. - WebSocket transport:
OcaFlyingFoxConnectionprovides client-side WebSocket connectivity on Apple platforms usingURLSessionWebSocketTask. - OCP.2 (AES70-4): the JSON protocol is a per-connection option (
OcaConnectionOptions(controlProtocol: .ocp2)) over TCP, WebSocket and the local loopback; the broker discovers_ocajson._tcpand_ocajsonws._tcpservices. See Documentation/OCP2.md. - Mach port transport:
Ocp1MachPortConnectionprovides fast local IPC between processes on macOS using Mach ports. - Property observation:
@OcaPropertyand@OcaBoundedPropertywrappers expose property changes asAsyncSequencestreams, enabling reactive UI updates. - JSON serialization: read the full state of any remote object or block tree as a JSON-compatible dictionary via
jsonObject. - Automatic reconnection: optionally reconnect when a connection drops or a device's IP address changes via mDNS, with configurable options for subscription refresh and object cache retention.
Device (SwiftOCADevice)
- Full AES70 device implementation: host actuators, sensors, blocks, matrices, managers, and agents.
@OcaDeviceProperty: property wrapper that manages local state and notifies connected controllers on changes.- Block and matrix containers:
OcaBlockandOcaMatrixfor organizing objects into hierarchical or grid-based topologies. - JSON serialization/deserialization: persist and restore device state via
serialize/deserializeand the parameter dataset API. - Multiple transport endpoints: run TCP, UDP, WebSocket, Unix domain socket, and Mach port (macOS) endpoints concurrently.
- OCP.2 (AES70-4) endpoints: pass
controlProtocol: .ocp2to the TCP or WebSocket endpoint to serve JSON controllers; they advertise the_ocajsonservice types. - TLS-secured TCP (
ocasec): PSK (AES70 baseline) and X.509 certificate credentials on both Apple and Linux, with optional mTLS and TLS 1.3 external PSK. See Documentation/TLS.md.
SwiftOCAUI
- Automatic view dispatch: the
OcaViewprotocol andOcaDetailViewselect specialized views based on OCA class type. - Pre-built actuator views: gain slider (log-scaled), mute toggle, polarity switch, pan/balance knob, boolean and float actuators.
- Sensor views: level meter with PPM ballistics (color-coded bar graph), identification sensor, and generic sensor displays.
- Block navigation: drill-down sidebar for hierarchical blocks; grid layout for leaf blocks; matrix navigation support.
- Bonjour discovery view: ready-made device browser view for listing and connecting to discovered devices.
Examples
Sample code can be found in Examples:
- OCADevice — a sample AES70 device with a gain control, boolean actuator matrix, and multiple transport endpoints.
- OCABrowser — a macOS SwiftUI app that discovers devices via Bonjour and provides a navigable block browser with specialized control views.
- OCABrokerTest — a command-line tool that discovers devices and auto-connects as they appear.
- Scripts/ocp2-nc.sh — a shell script that drives an OCP.2 (AES70-4) device with
nc, for poking at one without a controller.
ocacli is a command-line OCA controller that is implemented using SwiftOCA. SwiftOCA is also compatible with third-party OCA controllers such as AES70Explorer.
A Flutter wrapper is available here.
[OCABrowser]
Connecting to a device
A connection can be constructed from a hostname (or IP literal) and port. The name is resolved on each connect attempt — getaddrinfo for the socket backends, or natively by Network.framework (with Happy Eyeballs across the A/AAAA records) for OcaNWConnection — so a device whose address changes, or that is not yet reachable, is picked up automatically by the reconnection machinery:
import SwiftOCA
let connection = try await OcaTCPConnection(
host: "mixer.local",
port: 65000,
options: OcaConnectionOptions(flags: [.automaticReconnect])
)
try await connection.connect()Alternatively, construct a connection from a resolved socket address (deviceAddress) when you already have one — for example from an OcaConnectionBroker discovery event.
Walking the device tree
Given a connected connection, recursively print the role path of every object:
for actionObject in try await connection.rootBlock.resolveActionObjectsRecursive()
.compactMap({ $0.memberObject as? OcaOwnable }) {
try? await print("- \(actionObject.rolePathString)")
}Observing property changes
Subscribe to a gain property and react to changes:
let gain = try await connection.resolve(object: OcaGain.self, objectNumber: gainONo)
for try await value in gain.$gain {
print("gain changed to \(value) dB")
}Hosting a device
Create an AES70 device with a gain control and serve it over TCP:
import SwiftOCADevice
let device = OcaDevice.shared
try await device.initializeDefaultObjects()
let gain = try await OcaGain(
objectNumber: 10020,
role: "Main Gain",
deviceDelegate: device
)
let endpoint = try await OcaFlyingSocksStreamDeviceEndpoint(address: listenAddress)
try await endpoint.run()Talking to a device with nc
An OCP.2 device frames one JSON PDU per line, so a single nc is enough to poke at one — no client library, no framing to get right. Against the sample device's OCP.2 stream endpoint (port 65003):
$ echo '{"ProtocolVersion":1,"Commands":[{"Handle":1,"TargetONo":1,"MethodID":[3,4]}]}' \
| nc -w 2 localhost 65003
{"ProtocolVersion":1,"Responses":[{"StatusCode":"OK","Parameters":{"Name":"OCA Test"},"Handle":1}]}TargetONo 1 is OcaDeviceManager, MethodID is [DefLevel, Index] (3.4 is GetDeviceName), and Parameters are keyed by their AES70-2A names. Examples/Scripts/ocp2-nc.sh goes further: several commands in one PDU, CommandNRs, keep-alives, and an EV2 subscription that receives a notification. The WebSocket endpoint negotiates the AES70-OCP.2 subprotocol, so reach that one with websocat rather than nc.
Discovering devices with OcaConnectionBroker
Use OcaConnectionBroker to discover devices on the network and connect automatically:
import SwiftOCA
let broker = await OcaConnectionBroker(
connectionOptions: OcaConnectionOptions(flags: [
.automaticReconnect,
.refreshSubscriptionsOnReconnection,
])
)
for try await event in await broker.events {
switch event.eventType {
case .deviceAdded:
try await broker.connect(device: event.deviceIdentifier)
print("connected to \(event.deviceIdentifier.name)")
case .deviceRemoved:
print("lost \(event.deviceIdentifier.name)")
default:
break
}
}License
Apache License 2.0. See LICENSE.md.
Luke Howard <lukeh@lukktone.com>
Package Metadata
Repository: padl/swiftoca
Default branch: main
README: README.md