asyncapi: 3.0.0
info:
  title: Freedome media server — legacy socket.io profile (namespace /)
  version: 1.0.0
  description: >
    For installed builds of flutter_freedome 1.0 / 3.0, react-native-freedome
    0.1, razvivalka, churyavzrosliy and the

    Manager v3 backend clients. Engine.IO v4 / socket.io v5 at `/socket.io/`,
    namespace **`/`** (native clients use

    `/v1`, asyncapi/socketio.yaml). Auth: pairing token in `auth.token` or
    `?token=`; without one the anonymous guest

    policy applies (guest scopes; playback control only if the venue allows
    guests). The app id for

    integration-event comes from the handshake query `app` (else `payload.app`,
    else "unknown").

    Rules: legacy 0..100 volume / brightness are divided by 100 at the profile
    boundary (native 0..1); legacy dome ids

    map to the venue `default`; an unknown command type is answered with

    `command_response {success:false, error:"legacy-unsupported: <type>"}` —
    never silently ignored; app-level

    `heartbeat` is accepted and ignored (Engine.IO ping keeps the connection).
    Migration: docs/migration/legacy-profile.md.
  license:
    name: NativeMindNONC
    url: https://freedome.nativemind.us/license
defaultContentType: application/json
servers:
  mediaserver:
    host: '{host}:{port}'
    protocol: ws
    pathname: /socket.io/
    description: >-
      Media server listener (8080); the old default port 3000 only when
      `[legacy] listen_3000 = true` (C4, opt-in).
    variables:
      host:
        default: freedome.local
      port:
        default: '8080'
        enum:
          - '8080'
          - '3000'
    x-socketio:
      engineIO: 4
      protocol: 5
      path: /socket.io/
channels:
  root:
    address: /
    title: socket.io default namespace (legacy)
    servers:
      - $ref: '#/servers/mediaserver'
    messages:
      command:
        name: command
        title: command (C→S)
        payload:
          description: >-
            flutter_freedome FreeDomeCommand (calibration_models.dart:203) /
            react-native-freedome command; socket.io `command` and POST
            /api/command body, plain WS /ws/commands frame (1.0.0).
          type: object
          required:
            - type
          properties:
            type:
              type: string
              minLength: 1
              maxLength: 64
              description: >-
                play_content, pause_playback, set_volume, … unknown →
                legacy-unsupported
            data:
              type: object
            targetSystemId:
              type:
                - string
                - 'null'
            sessionId:
              type:
                - string
                - 'null'
            priority:
              type: integer
            timestamp:
              type:
                - string
                - 'null'
            timeout: {}
          additionalProperties: true
        examples:
          - name: set_volume
            payload:
              type: set_volume
              data:
                volume: 50
              targetSystemId: dome-1
              priority: 0
          - name: unknown
            payload:
              type: align_projectors
              data: {}
      command_response:
        name: command_response
        title: command_response (S→C, or the ack)
        payload:
          description: >-
            FreeDomeResponse (calibration_models.dart:220): socket.io
            `command_response` / ack / POST /api/command answer.
          type: object
          required:
            - success
          properties:
            success:
              type: boolean
            error:
              type:
                - string
                - 'null'
              description: 'unknown type → `legacy-unsupported: <type>`'
            data:
              type:
                - object
                - 'null'
            commandId:
              type:
                - string
                - 'null'
            timestamp:
              type:
                - string
                - 'null'
          additionalProperties: true
        examples:
          - name: ok
            payload:
              success: true
              commandId: set_volume
              timestamp: '2026-10-20T12:00:00.000Z'
          - name: legacy-unsupported
            payload:
              success: false
              error: 'legacy-unsupported: align_projectors'
              commandId: align_projectors
      system_event:
        name: system_event
        title: system_event (S→C)
        payload:
          description: >-
            flutter_freedome FreeDomeEvent pushed as socket.io `system_event`
            (native events rendered in the legacy shape).
          type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              minLength: 1
            data:
              type: object
            sourceSystemId:
              type:
                - string
                - 'null'
            timestamp:
              type:
                - string
                - 'null'
          additionalProperties: true
        examples:
          - name: output
            payload:
              type: dome_state_update
              data:
                volume: 50
                brightness: 80
              sourceSystemId: default
              timestamp: '2026-10-20T12:00:00.000Z'
      dome-state:
        name: dome-state
        title: dome-state (S→C)
        payload:
          description: >-
            `dome-state` (S→C): prisma DomeStatus shape of the old backend,
            composed from venues/default/output + players/main;
            `freedomeCommands` carries device commands for churyavzrosliy (←
            clients/{c}/commands).
          type: object
          required:
            - status
          properties:
            status:
              type: string
            currentMedia:
              anyOf:
                - type: string
                - type: object
                - type: 'null'
            position:
              type: number
              minimum: 0
            duration:
              type:
                - number
                - 'null'
            volume:
              type: number
              minimum: 0
              maximum: 100
              description: legacy 0..100
            brightness:
              type: number
              minimum: 0
              maximum: 100
              description: legacy 0..100
            rotation:
              type: number
            lightingEnabled:
              type: boolean
            activeProjectors:
              type: integer
              minimum: 0
            errors:
              type: array
              items:
                type: string
            freedomeCommands:
              type: array
              items:
                type: object
                required:
                  - type
                properties:
                  type:
                    enum:
                      - vibrate
                      - biometric_auth
                      - camera_capture
                      - sensor_data
                additionalProperties: true
          additionalProperties: true
        examples:
          - name: playing
            payload:
              status: playing
              currentMedia: intro
              position: 12.5
              duration: 600
              volume: 60
              brightness: 80
              rotation: 0
              lightingEnabled: false
              activeProjectors: 2
              errors: []
          - name: device-command
            payload:
              status: idle
              volume: 60
              brightness: 80
              freedomeCommands:
                - type: vibrate
                  intensity: 0.5
                  duration: 300
      integration-event:
        name: integration-event
        title: integration-event (both directions)
        payload:
          description: >-
            `integration-event` (both directions): app content; C→S →
            publishAppPayload(app = handshake query `app` | payload.app |
            "unknown"), S→C ← apps/{app} app.payload.
          type: object
          properties:
            app:
              type: string
            type:
              type: string
            data: {}
          additionalProperties: true
        examples:
          - name: razvivalka
            payload:
              app: razvivalka
              type: lesson
              data:
                title: Planets
                step: 2
      clear-integration:
        name: clear-integration
        title: clear-integration (C→S, no arguments)
        payload:
          description: Event without arguments (`clear-integration`, `getState`).
          type: 'null'
        examples:
          - name: clear
            payload: null
      brightness:
        name: brightness
        title: brightness (C→S, 0..100)
        payload:
          description: >-
            razvivalka `brightness` / `volume` (0..100) and `rotation` (degrees)
            emitted as a bare number.
          type: number
        examples:
          - name: seventy-five
            payload: 75
      volume:
        name: volume
        title: volume (C→S, 0..100)
        payload:
          description: >-
            razvivalka `brightness` / `volume` (0..100) and `rotation` (degrees)
            emitted as a bare number.
          type: number
        examples:
          - name: half
            payload: 50
      rotation:
        name: rotation
        title: rotation (C→S, degrees)
        payload:
          description: >-
            razvivalka `brightness` / `volume` (0..100) and `rotation` (degrees)
            emitted as a bare number.
          type: number
        examples:
          - name: ninety
            payload: 90
      heartbeat:
        name: heartbeat
        title: heartbeat (C→S, ignored)
        payload:
          description: >-
            react-native-freedome / flutter_freedome 1.0 `heartbeat` (ignored;
            Engine.IO ping is the heartbeat).
          type: object
          properties:
            timestamp:
              type: string
          additionalProperties: true
        examples:
          - name: rn
            payload:
              timestamp: '2026-10-20T12:00:00.000Z'
      getState:
        name: getState
        title: getState (C→S, Manager v3)
        payload:
          description: Event without arguments (`clear-integration`, `getState`).
          type: 'null'
        examples:
          - name: get
            payload: null
      setState:
        name: setState
        title: setState (C→S, Manager v3)
        payload:
          description: >-
            Manager backend v3 socket `state` (answer to getState / after
            setState).
          type: object
          properties:
            brightness:
              type: number
            volume:
              type: number
            rotation:
              type: number
            playing:
              type: boolean
            currentMedia: {}
          additionalProperties: true
        examples:
          - name: brightness
            payload:
              brightness: 80
      state:
        name: state
        title: state (S→C, Manager v3)
        payload:
          description: >-
            Manager backend v3 socket `state` (answer to getState / after
            setState).
          type: object
          properties:
            brightness:
              type: number
            volume:
              type: number
            rotation:
              type: number
            playing:
              type: boolean
            currentMedia: {}
          additionalProperties: true
        examples:
          - name: state
            payload:
              brightness: 80
              volume: 50
              rotation: 0
              playing: true
      sendOsc:
        name: sendOsc
        title: sendOsc (C→S, Manager v3)
        payload:
          description: >-
            Manager backend v3 socket `sendOsc` → native sendOsc (configured
            targets only).
          type: object
          required:
            - address
          properties:
            address:
              type: string
              pattern: ^/
            args:
              type: array
            host:
              type: string
            port:
              type: integer
          additionalProperties: true
        examples:
          - name: cue
            payload:
              address: /cue/1/go
              args:
                - 1
      oscSent:
        name: oscSent
        title: oscSent (S→C, Manager v3)
        payload:
          description: >-
            Manager backend v3 socket `sendOsc` → native sendOsc (configured
            targets only).
          type: object
          required:
            - address
          properties:
            address:
              type: string
              pattern: ^/
            args:
              type: array
            host:
              type: string
            port:
              type: integer
          additionalProperties: true
        examples:
          - name: sent
            payload:
              address: /cue/1/go
              args:
                - 1
      error:
        name: error
        title: error (S→C, Manager v3)
        payload:
          type: object
          properties:
            message:
              type: string
          additionalProperties: true
        examples:
          - name: forbidden
            payload:
              message: 'forbidden: missing scope gateways:manage'
operations:
  emitCommand:
    action: send
    channel:
      $ref: '#/channels/root'
    summary: >-
      flutter_freedome / react-native-freedome command (RN with ack callback,
      flutter_freedome 3.0 answers via command_response).
    messages:
      - $ref: '#/channels/root/messages/command'
    reply:
      channel:
        $ref: '#/channels/root'
      messages:
        - $ref: '#/channels/root/messages/command_response'
  receiveSystemEvent:
    action: receive
    channel:
      $ref: '#/channels/root'
    messages:
      - $ref: '#/channels/root/messages/system_event'
  receiveDomeState:
    action: receive
    channel:
      $ref: '#/channels/root'
    summary: >-
      Snapshot of venues/default/output + players/main (+ device commands) on
      every change.
    messages:
      - $ref: '#/channels/root/messages/dome-state'
  emitIntegrationEvent:
    action: send
    channel:
      $ref: '#/channels/root'
    summary: App content → publishAppPayload.
    messages:
      - $ref: '#/channels/root/messages/integration-event'
  receiveIntegrationEvent:
    action: receive
    channel:
      $ref: '#/channels/root'
    summary: apps/{app} app.payload rendered as integration-event.
    messages:
      - $ref: '#/channels/root/messages/integration-event'
  emitClearIntegration:
    action: send
    channel:
      $ref: '#/channels/root'
    messages:
      - $ref: '#/channels/root/messages/clear-integration'
  emitOutputNumbers:
    action: send
    channel:
      $ref: '#/channels/root'
    summary: >-
      razvivalka brightness / volume (0..100) and rotation (degrees) →
      patchOutput.
    messages:
      - $ref: '#/channels/root/messages/brightness'
      - $ref: '#/channels/root/messages/volume'
      - $ref: '#/channels/root/messages/rotation'
  emitHeartbeat:
    action: send
    channel:
      $ref: '#/channels/root'
    messages:
      - $ref: '#/channels/root/messages/heartbeat'
  emitV3State:
    action: send
    channel:
      $ref: '#/channels/root'
    summary: Manager backend v3 getState / setState → state.
    messages:
      - $ref: '#/channels/root/messages/getState'
      - $ref: '#/channels/root/messages/setState'
    reply:
      channel:
        $ref: '#/channels/root'
      messages:
        - $ref: '#/channels/root/messages/state'
        - $ref: '#/channels/root/messages/error'
  emitV3SendOsc:
    action: send
    channel:
      $ref: '#/channels/root'
    messages:
      - $ref: '#/channels/root/messages/sendOsc'
    reply:
      channel:
        $ref: '#/channels/root'
      messages:
        - $ref: '#/channels/root/messages/oscSent'
        - $ref: '#/channels/root/messages/error'
components:
  messages:
    command:
      name: command
      title: command (C→S)
      payload:
        description: >-
          flutter_freedome FreeDomeCommand (calibration_models.dart:203) /
          react-native-freedome command; socket.io `command` and POST
          /api/command body, plain WS /ws/commands frame (1.0.0).
        type: object
        required:
          - type
        properties:
          type:
            type: string
            minLength: 1
            maxLength: 64
            description: >-
              play_content, pause_playback, set_volume, … unknown →
              legacy-unsupported
          data:
            type: object
          targetSystemId:
            type:
              - string
              - 'null'
          sessionId:
            type:
              - string
              - 'null'
          priority:
            type: integer
          timestamp:
            type:
              - string
              - 'null'
          timeout: {}
        additionalProperties: true
      examples:
        - name: set_volume
          payload:
            type: set_volume
            data:
              volume: 50
            targetSystemId: dome-1
            priority: 0
        - name: unknown
          payload:
            type: align_projectors
            data: {}
    command_response:
      name: command_response
      title: command_response (S→C, or the ack)
      payload:
        description: >-
          FreeDomeResponse (calibration_models.dart:220): socket.io
          `command_response` / ack / POST /api/command answer.
        type: object
        required:
          - success
        properties:
          success:
            type: boolean
          error:
            type:
              - string
              - 'null'
            description: 'unknown type → `legacy-unsupported: <type>`'
          data:
            type:
              - object
              - 'null'
          commandId:
            type:
              - string
              - 'null'
          timestamp:
            type:
              - string
              - 'null'
        additionalProperties: true
      examples:
        - name: ok
          payload:
            success: true
            commandId: set_volume
            timestamp: '2026-10-20T12:00:00.000Z'
        - name: legacy-unsupported
          payload:
            success: false
            error: 'legacy-unsupported: align_projectors'
            commandId: align_projectors
    system_event:
      name: system_event
      title: system_event (S→C)
      payload:
        description: >-
          flutter_freedome FreeDomeEvent pushed as socket.io `system_event`
          (native events rendered in the legacy shape).
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            minLength: 1
          data:
            type: object
          sourceSystemId:
            type:
              - string
              - 'null'
          timestamp:
            type:
              - string
              - 'null'
        additionalProperties: true
      examples:
        - name: output
          payload:
            type: dome_state_update
            data:
              volume: 50
              brightness: 80
            sourceSystemId: default
            timestamp: '2026-10-20T12:00:00.000Z'
    dome-state:
      name: dome-state
      title: dome-state (S→C)
      payload:
        description: >-
          `dome-state` (S→C): prisma DomeStatus shape of the old backend,
          composed from venues/default/output + players/main; `freedomeCommands`
          carries device commands for churyavzrosliy (← clients/{c}/commands).
        type: object
        required:
          - status
        properties:
          status:
            type: string
          currentMedia:
            anyOf:
              - type: string
              - type: object
              - type: 'null'
          position:
            type: number
            minimum: 0
          duration:
            type:
              - number
              - 'null'
          volume:
            type: number
            minimum: 0
            maximum: 100
            description: legacy 0..100
          brightness:
            type: number
            minimum: 0
            maximum: 100
            description: legacy 0..100
          rotation:
            type: number
          lightingEnabled:
            type: boolean
          activeProjectors:
            type: integer
            minimum: 0
          errors:
            type: array
            items:
              type: string
          freedomeCommands:
            type: array
            items:
              type: object
              required:
                - type
              properties:
                type:
                  enum:
                    - vibrate
                    - biometric_auth
                    - camera_capture
                    - sensor_data
              additionalProperties: true
        additionalProperties: true
      examples:
        - name: playing
          payload:
            status: playing
            currentMedia: intro
            position: 12.5
            duration: 600
            volume: 60
            brightness: 80
            rotation: 0
            lightingEnabled: false
            activeProjectors: 2
            errors: []
        - name: device-command
          payload:
            status: idle
            volume: 60
            brightness: 80
            freedomeCommands:
              - type: vibrate
                intensity: 0.5
                duration: 300
    integration-event:
      name: integration-event
      title: integration-event (both directions)
      payload:
        description: >-
          `integration-event` (both directions): app content; C→S →
          publishAppPayload(app = handshake query `app` | payload.app |
          "unknown"), S→C ← apps/{app} app.payload.
        type: object
        properties:
          app:
            type: string
          type:
            type: string
          data: {}
        additionalProperties: true
      examples:
        - name: razvivalka
          payload:
            app: razvivalka
            type: lesson
            data:
              title: Planets
              step: 2
    clear-integration:
      name: clear-integration
      title: clear-integration (C→S, no arguments)
      payload:
        description: Event without arguments (`clear-integration`, `getState`).
        type: 'null'
      examples:
        - name: clear
          payload: null
    brightness:
      name: brightness
      title: brightness (C→S, 0..100)
      payload:
        description: >-
          razvivalka `brightness` / `volume` (0..100) and `rotation` (degrees)
          emitted as a bare number.
        type: number
      examples:
        - name: seventy-five
          payload: 75
    volume:
      name: volume
      title: volume (C→S, 0..100)
      payload:
        description: >-
          razvivalka `brightness` / `volume` (0..100) and `rotation` (degrees)
          emitted as a bare number.
        type: number
      examples:
        - name: half
          payload: 50
    rotation:
      name: rotation
      title: rotation (C→S, degrees)
      payload:
        description: >-
          razvivalka `brightness` / `volume` (0..100) and `rotation` (degrees)
          emitted as a bare number.
        type: number
      examples:
        - name: ninety
          payload: 90
    heartbeat:
      name: heartbeat
      title: heartbeat (C→S, ignored)
      payload:
        description: >-
          react-native-freedome / flutter_freedome 1.0 `heartbeat` (ignored;
          Engine.IO ping is the heartbeat).
        type: object
        properties:
          timestamp:
            type: string
        additionalProperties: true
      examples:
        - name: rn
          payload:
            timestamp: '2026-10-20T12:00:00.000Z'
    getState:
      name: getState
      title: getState (C→S, Manager v3)
      payload:
        description: Event without arguments (`clear-integration`, `getState`).
        type: 'null'
      examples:
        - name: get
          payload: null
    setState:
      name: setState
      title: setState (C→S, Manager v3)
      payload:
        description: >-
          Manager backend v3 socket `state` (answer to getState / after
          setState).
        type: object
        properties:
          brightness:
            type: number
          volume:
            type: number
          rotation:
            type: number
          playing:
            type: boolean
          currentMedia: {}
        additionalProperties: true
      examples:
        - name: brightness
          payload:
            brightness: 80
    state:
      name: state
      title: state (S→C, Manager v3)
      payload:
        description: >-
          Manager backend v3 socket `state` (answer to getState / after
          setState).
        type: object
        properties:
          brightness:
            type: number
          volume:
            type: number
          rotation:
            type: number
          playing:
            type: boolean
          currentMedia: {}
        additionalProperties: true
      examples:
        - name: state
          payload:
            brightness: 80
            volume: 50
            rotation: 0
            playing: true
    sendOsc:
      name: sendOsc
      title: sendOsc (C→S, Manager v3)
      payload:
        description: >-
          Manager backend v3 socket `sendOsc` → native sendOsc (configured
          targets only).
        type: object
        required:
          - address
        properties:
          address:
            type: string
            pattern: ^/
          args:
            type: array
          host:
            type: string
          port:
            type: integer
        additionalProperties: true
      examples:
        - name: cue
          payload:
            address: /cue/1/go
            args:
              - 1
    oscSent:
      name: oscSent
      title: oscSent (S→C, Manager v3)
      payload:
        description: >-
          Manager backend v3 socket `sendOsc` → native sendOsc (configured
          targets only).
        type: object
        required:
          - address
        properties:
          address:
            type: string
            pattern: ^/
          args:
            type: array
          host:
            type: string
          port:
            type: integer
        additionalProperties: true
      examples:
        - name: sent
          payload:
            address: /cue/1/go
            args:
              - 1
    error:
      name: error
      title: error (S→C, Manager v3)
      payload:
        type: object
        properties:
          message:
            type: string
        additionalProperties: true
      examples:
        - name: forbidden
          payload:
            message: 'forbidden: missing scope gateways:manage'
