PTZ, Wake-up and Bitrate
Pre-requisite: The user has to be signed in to perform the following operations.
Operate PTZ of the Device
To move the PTZ of the device, The following method can be used:
spaceId(required): The space ID of the device.deviceId(required): The device ID.request(required):MovePtzRequest(pan:tilt:nonStop:)— how far to move on each axis, and whether the camera should keep going until it is told to stop.
Factory.deviceService.movePtz(spaceId: String, deviceId: String, request: MovePtzRequest) -> IVPublisher<Void>
pan and tilt are not directions, they are distances. How far one press should move the camera comes from its ptz cluster, so a device that reports clusters needs its pan and tilt scaled from the cluster before the request is sent:
Helper on DeviceModel | What it gives you |
|---|---|
supportPTZ | Whether the camera has a ptz cluster at all |
supportsPTZPan, supportsPTZTilt | Which axes it can move — build the D-pad from these |
ptzPanMultiplier, ptzTiltMultiplier | The distance of one step on each axis |
ptzPanStepsCount, ptzTiltStepsCount | The full travel of each axis, max - min of the attribute |
flipImage | Whether the image is rotated, from the rotationalAngle cluster |
A camera only reports clusters from clusterGroupVersion v1 onwards; an older one has none of these values and takes the raw direction unscaled, so gate the scaling on the version:
extension DeviceModel {
var supportsClusters: Bool {
let version = clusterGroupVersion ?? "v0"
return !version.isEmpty && version.lowercased() != "v0"
}
}
Which of the two distances you use is what separates a step from a continuous move:
func movePtz(_ device: DeviceModel, pan: Int, tilt: Int, nonStop: Bool) {
var panValue = pan // -1, 0 or 1 — the direction the user pressed
var tiltValue = tilt
if device.supportsClusters {
if nonStop {
// Continuous: send the whole travel range so the camera keeps moving
// until stopPtz is called.
panValue = -(device.ptzPanStepsCount ?? 4000) * pan
tiltValue = -(device.ptzTiltStepsCount ?? 4000) * tilt
} else {
// Single step: one nudge in that direction.
panValue = (device.ptzPanMultiplier ?? -100) * pan
tiltValue = (device.ptzTiltMultiplier ?? -50) * tilt
}
// A camera mounted upside down sees the user's "up" as its own "down".
if device.flipImage {
panValue = -panValue
tiltValue = -tiltValue
}
}
Factory.deviceService.movePtz(
spaceId: device.spaceId,
deviceId: device.id,
request: MovePtzRequest(pan: panValue, tilt: tiltValue, nonStop: nonStop)
)
.sink { _ in }
.store(in: &cancellables)
}
For nonStop: true use the steps count, not the multiplier. The camera moves the distance it is given, so a continuous move is expressed as the axis’s entire travel — anything smaller stops early, part way through the press. The fallbacks of 4000 are deliberately larger than any real range, for a camera whose cluster does not report min and max.
A nonStop move must be stopped by you. Nothing ends it but stopPtz, so pair every one with a stop when the user lets go:
// User presses and holds a direction
movePtz(device, pan: 1, tilt: 0, nonStop: true)
// User releases
Factory.deviceService.stopPtz(spaceId: device.spaceId, deviceId: device.id)
.sink { _ in }
.store(in: &cancellables)
A short tap is the other case: send nonStop: false with the multiplier and no stop is needed — the camera moves one step and settles. The reference app decides between them with a timer, treating a press held longer than 0.4 seconds as continuous and anything shorter as a single step.
flipImagereads therotationalAnglecluster, which is the same value the live view uses to rotate the picture. Apply it to the request rather than to the UI: the buttons should keep meaning what they say, and it is the camera’s idea of up that has changed.
Stop PTZ Movement
Ends a continuous move. Required after every movePtz sent with nonStop: true, and harmless otherwise.
To stop the PTZ movement of the device, The following method can be used:
spaceId(required): The space ID of the device.deviceId(required): The device ID.
Factory.deviceService.stopPtz(spaceId: String, deviceId: String) -> IVPublisher<Void>
Reset PTZ to default position
To reset the PTZ of the device, The following method can be used:
spaceId(required): The space ID of the device.deviceId(required): The device ID.
Factory.deviceService.resetPtz(spaceId: String, deviceId: String) -> IVPublisher<Void>
Wake Up Device from sleep state
To wake up the device, The following method can be used:
spaceId(required): The space ID of the device.deviceId(required): The device ID.request(required):WakeUpRequest(reason:)— a reason string recorded against the device, e.g."ChangeSettings"or"LiveViewStart".
Factory.deviceService.wakeup(spaceId: String, deviceId: String, request: WakeUpRequest) -> IVPublisher<Void>
Wake Up the Device and send a follow up request
To wake the device and deliver a follow up payload in the same round trip, The following method can be used:
spaceId(required): The space ID of the device.deviceId(required): The device ID.wakeUpRequest(required): The request object containing the reason for the wake up.request(optional): The payload to be delivered once the device is awake.
Factory.deviceService.wakeupDevice(spaceId: String, deviceId: String, wakeUpRequest: WakeUpRequest, request: Codable?) -> IVPublisher<Codable?>
Set the Device Bitrate
To set the streaming bitrate of the device, The following method can be used:
spaceId(required): The space ID of the device.deviceId(required): The device ID.request(required): The request object containing the bitrate.BitrateRequest(bitrate:)takes the raw value:Bitrate.auto.rawValue("Auto") orBitrate.hd.rawValue("HD").
Factory.deviceService.setDeviceBitrate(spaceId: String, deviceId: String, request: BitrateRequest) -> IVPublisher<Void>
The lullaby player is controlled through the same service; it is documented with the Baby product under Lullaby.