Summary

The Linn Generic DS Series is a network audio player supporting LPEC (Linn Protocol over IP) control via raw TCP socket on port 23. The protocol uses an action/response model with SUBSCRIBE-based eventing for state changes. Supports up to 4 simultaneous LPEC sessions per device.

Transport

protocols:
  - tcp
addressing:
  port: 23
auth:
  type: none  # inferred: no auth procedure in source

Traits

# Evidence-based traits:
# - routable    (Ds/Product source selection commands present)
# - queryable   (GetVolume, Source, GetIdArray, preamp_volume_query present)
# - levelable   (SetVolume on RenderingControl and Preamp)
# - powerable   UNRESOLVED: no explicit SetStandby / power on/off in source; ProductStandby is reported via EVENT but no documented ACTION mutates it
routable: true
queryable: true
levelable: true

Actions

# LPEC ACTION format: ACTION [sub-device]/[service] [version] [action] "[inarg1]" "[inarg2]" ...
# All args XML-escaped and double-quoted per source.

# MediaRenderer/AVTransport (UPnP) - transport state:
- id: play
  label: Play
  kind: action
  command: "ACTION MediaRenderer/AVTransport 1 Play \"{instance_id}\" \"{speed}\""
  params:
    - name: instance_id
      type: string
      description: UPnP instance ID (typically "0")
    - name: speed
      type: string
      description: Play speed (typically "1")

- id: pause
  label: Pause
  kind: action
  command: "ACTION MediaRenderer/AVTransport 1 Pause \"{instance_id}\""
  params:
    - name: instance_id
      type: string
      description: UPnP instance ID (typically "0")

- id: next
  label: Next
  kind: action
  command: "ACTION MediaRenderer/AVTransport 1 Next \"{instance_id}\""
  params:
    - name: instance_id
      type: string
      description: UPnP instance ID (typically "0")

- id: previous
  label: Previous
  kind: action
  command: "ACTION MediaRenderer/AVTransport 1 Previous \"{instance_id}\""
  params:
    - name: instance_id
      type: string
      description: UPnP instance ID (typically "0")

# MediaRenderer/RenderingControl (UPnP) - volume:
- id: get_volume
  label: Get Volume (RenderingControl)
  kind: action
  command: "ACTION MediaRenderer/RenderingControl 1 GetVolume \"{instance_id}\" \"{channel}\""
  params:
    - name: instance_id
      type: string
    - name: channel
      type: string
      description: Channel name (e.g., "Master")

- id: set_volume
  label: Set Volume (RenderingControl)
  kind: action
  command: "ACTION MediaRenderer/RenderingControl 1 SetVolume \"{instance_id}\" \"{channel}\" \"{desired_volume}\""
  params:
    - name: instance_id
      type: string
    - name: channel
      type: string
    - name: desired_volume
      type: integer
      description: Volume level (0-100)

# Preamp/Preamp (Linn-specific) - preamp state:
- id: preamp_volume_query
  label: Preamp Volume Query
  kind: action
  command: "ACTION Preamp/Preamp 1 Volume"
  params: []
  # Response: RESPONSE "[volume]"

- id: set_mute
  label: Set Mute (Preamp)
  kind: action
  command: "ACTION Preamp/Preamp 1 SetMute \"{muted}\""
  params:
    - name: muted
      type: boolean
      description: "true" to mute, "false" to unmute

# Ds/Product - source selection:
- id: set_source_index
  label: Set Source Index
  kind: action
  command: "ACTION Ds/Product 1 SetSourceIndex \"{source_index}\""
  params:
    - name: source_index
      type: integer
      description: Source number (0-based; subject to change with firmware or source visibility)

- id: set_source_by_system_name
  label: Set Source By System Name
  kind: action
  command: "ACTION Ds/Product 2 SetSourceBySystemName \"{system_name}\""
  params:
    - name: system_name
      type: string
      description: System source name (e.g., "Balanced", "Radio")

- id: set_source_index_by_name
  label: Set Source Index By Name
  kind: action
  command: "ACTION Ds/Product 2 SetSourceIndexByName \"{source_name}\""
  params:
    - name: source_name
      type: string
      description: Source name (e.g., "CD12")

- id: source_query
  label: Query Source
  kind: action
  command: "ACTION Ds/Product 2 Source \"{source_index}\""
  params:
    - name: source_index
      type: integer
      description: Source number to query

# Ds/Pins - PIN presets (Davaar 67+):
- id: invoke_pin
  label: Invoke PIN
  kind: action
  command: "ACTION Ds/Pins 1 InvokeId \"{pin_id}\""
  params:
    - name: pin_id
      type: integer
      description: PIN number to invoke

- id: read_pin_list
  label: Read PIN List
  kind: action
  command: "ACTION Ds/Pins 1 ReadList \"{pin_array_filter}\""
  params:
    - name: pin_array_filter
      type: string
      description: Filter string for PIN list, e.g. "[ 1]"

- id: get_pin_id_array
  label: Get PIN ID Array
  kind: action
  command: "ACTION Ds/Pins 1 GetIdArray"
  params: []

# Protocol-level messages:
- id: subscribe
  label: Subscribe
  kind: action
  command: "SUBSCRIBE {sub_device_service}"
  params:
    - name: sub_device_service
      type: string
      description: "[sub-device]/[service]" e.g. "Ds/Product"

- id: unsubscribe
  label: Unsubscribe
  kind: action
  command: "UNSUBSCRIBE {subscription_id_or_service}"
  params:
    - name: subscription_id_or_service
      type: string
      description: Subscription ID (e.g. "49") or "[sub-device]/[service]" (e.g. "Ds/Product"). Leave empty to unsubscribe from all.

Feedbacks

# Response to ACTION with no outargs:
- id: response_empty
  type: object
  description: 'RESPONSE <CR><LF> for ACTION with no outargs (e.g. SetVolume, SetMute)'

# Response to ACTION with outargs:
- id: response
  type: object
  description: 'RESPONSE "[outarg1]" "[outarg2]" ... "[outargn]"'
  properties:
    - name: outargs
      type: array
      description: Ordered list of output argument strings

# Response to SUBSCRIBE:
- id: subscribe_response
  type: object
  description: 'SUBSCRIBE [subscription-id]'
  properties:
    - name: subscription_id
      type: integer
      description: Numeric ID; use to correlate EVENT messages and to UNSUBSCRIBE

# Response to UNSUBSCRIBE (one per unsubscribed service; multiple may arrive for "UNSUBSCRIBE" with no args):
- id: unsubscribe_response
  type: object
  description: 'UNSUBSCRIBE [subscription-id]'
  properties:
    - name: subscription_id
      type: integer

# Event - initial state dump (sequence 0):
- id: event_initial
  type: object
  description: 'EVENT [subscription-id] 0 [var1-name] "[var1]" [var2-name] "[var2]" ...'
  properties:
    - name: subscription_id
      type: integer
    - name: sequence_no
      type: integer
      description: 0 = initial state dump; all evented variables for the service are reported
    - name: variables
      type: object
      description: Key-value pairs of all evented variables

# Event - change notification (sequence > 0):
- id: event_change
  type: object
  description: 'EVENT [subscription-id] [sequence-no] [var1-name] "[var1]" ...'
  properties:
    - name: subscription_id
      type: integer
    - name: sequence_no
      type: integer
      description: 32-bit unsigned; wraps to 1 after 2^32-1; increments per EVENT
    - name: variables
      type: object
      description: Key-value pairs of changed variables only

# Error responses (23 codes defined in source):
- id: error
  type: object
  description: 'ERROR [code] "[description]"'
  properties:
    - name: code
      type: integer
      description: |
        Defined codes: 101 "Command not recognised", 102 "Service not specified",
        103 "Service not found", 104 "Version invalid", 105 "Version not specified",
        106 "Version not supported", 107 "Method not specified",
        108 "Method execution exception", 201 "Boolean argument invalid",
        202 "String argument invalid", 203 "Unsigned numeric argument invalid",
        204 "Signed numeric invalid", 205 "Binary argument invalid",
        206 "Invalid argument escaping", 301 "Argument list incomplete",
        302 "Argument not quoted", 303 "Argument incomplete",
        401 "Already subscribed", 402 "Client has too many subscriptions",
        403 "Service has too many subscriptions", 404 "Subscription not found",
        405 "Service not subscribed", 406 "Invalid XML escaping".
        Service-specific ERROR messages may also be sent.
    - name: description
      type: string

# Discovery - sub-device presence:
- id: alive
  type: object
  description: 'ALIVE [sub-device] [udn] - sent on connect for each enabled sub-device, and on re-enable'
  properties:
    - name: sub_device
      type: string
    - name: udn
      type: string
      description: Unique Device Name (URN format, e.g. "4c494e4e-0050-c221-71e5-df000003013f")

# Discovery - sub-device removal:
- id: bye
  label: BYEBYE
  type: object
  description: 'BYEBYE [sub-device] [udn] - sent on sub-device disable, and for all sub-devices on reboot (after which LPEC connection is closed by device)'
  properties:
    - name: sub_device
      type: string
    - name: udn
      type: string

Variables

# Variables exposed via EVENT messages after SUBSCRIBE.
# From source EVENT example (Ds/Product service):
- id: ProductName
  type: string
  description: Friendly product name (e.g. "Sneaky Music DS")
- id: ProductRoom
  type: string
  description: Room name the product belongs to (e.g. "Main Room")
- id: ProductStandby
  type: boolean
  description: true when product is in standby
- id: ProductSourceIndex
  type: integer
  description: Currently selected source index
# From source example (Preamp/Preamp service):
- id: Volume
  type: integer
  description: Preamp volume (0-100)

Events

# Subscriptions produce unsolicited EVENT messages when variables change.
# Format: EVENT [subscription-id] [sequence-no] [var1-name] "[var1]" ...
# Only changed variables are reported.
# A sequence number of 0 indicates the initial state dump (all evented variables).
# Sequence numbers wrap around to 1 after reaching 2^32-1.
#
# Sub-device lifecycle messages on the same LPEC connection:
# - ALIVE [sub-device] [udn]    on initial connect (per enabled sub-device), and on re-enable
# - BYEBYE [sub-device] [udn]   on sub-device disable, and for all sub-devices on reboot
#   (followed by LPEC connection close on reboot)
# When a sub-device is disabled, all subscriptions to its services are forcibly
# revoked - BYEBYE is commonly preceded by a number of unsolicited UNSUBSCRIBE messages.
#
# Subscription limits:
# - Up to 16 services may be subscribed simultaneously per device
# - Up to 4 simultaneous LPEC sessions per device

Macros

# PIN-based presets (Ds/Pins service):
# - Invoke a PIN preset by ID
# - Read PIN contents with filter
# - Get live PIN ID array
# Note: PIN numbers may change after reboot if recently altered.
# Requires Davaar 67+ for PIN LPEC functions.

Safety

confirmation_required_for: []
interlocks: []
# UNRESOLVED: no safety warnings or interlock procedures stated in source

Notes

The LPEC protocol is accessed via raw TCP socket on port 23. Each product supports up to 4 simultaneous LPEC sessions. A known bug causes the first command after initial ALIVE messages to generate an error — workaround is to send a blank command immediately after ALIVE messages are received.

Source selection via SetSourceIndex is not stable across firmware versions or when sources are disabled. Use SetSourceBySystemName or SetSourceIndexByName for reliable source selection in Davaar 50+.

Service discovery is done via UPnP, with device descriptions available at http://[device-ip]:55178/Ds/device.xml. Specific service descriptions (e.g., Media, Preamp) are at corresponding paths.

Provenance

source_domains:
  - docs.linn.co.uk
source_urls:
  - https://docs.linn.co.uk/wiki/index.php/Developer:LPEC
retrieved_at: 2026-06-03T13:49:00.798Z
last_checked_at: 2026-06-04T06:27:09.603Z

Verification Summary

verdict: verified
checked_at: 2026-06-04T06:27:09.603Z
matched_actions: 17
action_count: 17
confidence: medium
summary: "All 17 spec actions matched literal source commands with correct transport parameters (TCP port 23, no auth). (4 unresolved item(s) noted in Known Gaps.)"

Known Gaps

- "list any major gaps here"
- "no explicit SetStandby / power on/off in source; ProductStandby is reported via EVENT but no documented ACTION mutates it"
- "no safety warnings or interlock procedures stated in source"
- "device.xml and service.xml URLs are derived from the protocol spec example — verify against actual device"

From the AI4AV catalog (https://ai4av.net) · ODbL-1.0