Libratone Zipp (v1) network API

This is an attempt to document the network protocol used to control Libratone Zipp (v1) WiFi speakers, e.g. by the Libratone Android app (com.libratone, version 8.3.1).

The main control protocol is a binary UDP protocol ("LUCI"), not HTTP. HTTP is only used for a status page, UPnP and the initial network setup / firmware update.

API basics

Overview

A Zipp is controlled by sending small UDP datagrams to it. It answers (and sends state changes) via UDP datagrams to the controlling host. All of that happens inside the local network; there is no cloud involved for the basic control.

Ports

Port

Protocol

Purpose

7777 (speaker)

UDP

Commands to the speaker

7778 (client)

UDP

Replies to commands from the speaker

3333 (client)

UDP

Notifications from the speaker

3334 (speaker)

UDP

Client acknowledges notifications

1800 (multicast)

UDP

Discovery (239.255.255.250:1800)

80 (speaker)

TCP

Web page /cgi?Overview

49152 (speaker)

TCP

UPnP description.xml

Important points:

  • Replies are not sent back to the source port of the request. The speaker always sends replies to port 7778 of the requesting IP address, and notifications to port 3333. A client thus has to listen on 7778 and 3333, and can only have one such program per host running at a time unless it uses SO_REUSEADDR/SO_REUSEPORT.

  • Requests may be sent from any source port.

  • The speaker needs to know the IP address of the client to be able to send replies. The Android app sends its first command and the speaker remembers the sender, see Registration.

Typical communication

  1. Find the speaker with Discovery (or use a fixed IP).

  2. Send fetch packets to speaker:7777, e.g. for volume, name, play state.

  3. Read the replies on 7778.

  4. Listen on 3333 for notifications, and acknowledge them on speaker:3334.

  5. Send set packets to speaker:7777 to change settings. The speaker confirms the change with a reply on 7778 and/or a notification on 3333.

Android app implementation

com.libratone.v3.luci.LUCIControl sends commands to port 7777 (SendCommand/SendLUCICommand), acknowledgements to port 3334 (SendPacket/SendRespCommand), and runs a ServerThread on 3333. com.libratone.v3.luci.UdpMonitor handles the multicast discovery on 1800.

Unanswered requests are re-sent by the app after a timeout (LUCIControl.manageSentPacket), UDP being unreliable. The random 16 bit value in the packet header (see Packet format) is used to match replies to requests there.

Packet format

All commands, replies and notifications on ports 7777, 7778, 3333 and 3334 use the same packet structure ("LUCI packet", com.libratone.v3.luci.LUCIPacket in the app). All multi-byte numbers are big endian.

Header (10 bytes)

Offset

Length

Content

0

2

Remote ID, always 0xAAAA (43690)

2

1

Command type, see below

3

2

Command ID, e.g. 0x0040 (64) for volume

5

1

Command status, normally 0x00

6

2

"CRC", not a checksum. The app puts a random number between 1 and 65535 here. Implementations use fixed values (0x1234, 0x0000) without problems.

8

2

Length of the data section

10

n

Data

The data length field is exact; a packet is only valid when len(packet) == 10 + data_length.

The data is almost always printable ASCII or UTF-8 text: numbers are sent as decimal strings ("64", not byte 0x40), flags are the characters "0" and "1", structured data is JSON. Exceptions are documented per command.

Command type

Value

Name

Meaning

1

fetch

Request the current value. The data section is empty.

2

set

Change a value. Also used for replies and notifications and thus can be seen in packets coming from the speaker.

The app code mentions further values (e.g. 3, 5) in LSSDPNode.fetch()/set(), but these select the transport (USB, Bluetooth, GAIA/BLE for other speaker families), not the UDP packet type.

Command ID

The same ID is used for fetch, set and the reply. There are some exceptions, e.g. the player info (set: 277, fetch: 278) or battery level (fetch: 256, reply: 257). Those are noted at the commands.

Examples

Set volume to 50 % (type=2, command=64, data "50"):

aa aa 02 00 40 00 12 34 00 02 35 30

Fetch the speaker name (type=1, command=90):

aa aa 01 00 5a 00 12 34 00 00

Reply with the name Kitchen:

aa aa 02 00 5a 00 xx xx 00 07 4b 69 74 63 68 65 6e

Note that the Loxone wiki shows the same commands with 00 00 instead of aa aa at the start: the speaker does not seem to check the remote ID.

Common data formats

bool

The ASCII characters "1" (true) and "0" (false).

number

Decimal number as ASCII string, e.g. "100". No leading zeros are required, although the Loxone wiki shows two digit values for volumes below 10 ("05"). Both work.

json

UTF-8 encoded JSON, not newline-terminated.

voicingId

Identifier of a sound profile, e.g. V100. See Voicing.

zoneId / linkId

Name of a multi-room group, e.g. LINK ZippMiniOne_xxx (see Discovery).

Replies and notifications carry the same data format as set requests unless noted otherwise.

UDP is unreliable and the speaker may not answer fetch commands it does not support, so clients should use timeouts and retries.

Discovery

Speakers announce themselves with a SSDP-like protocol on the multicast group 239.255.255.250, but on port 1800 instead of the standard 1900.

Search request

Clients send a datagram with the text:

M-SEARCH * HTTP/1.1

to 239.255.255.250:1800. Further headers are not required. (The Android app builds the first line from a version string, HTTP/1.2 or HTTP/1.1, followed by HOST: 239.255.255.250:1800 and PROTOCOL:Version 1.0.)

The reply of the speaker is a NOTIFY message as shown below. The client socket must be bound to port 1800 to see it (libratone-rs binds its search socket to 1800 with SO_REUSEPORT).

Announcements

Speakers also send the same message unsolicited when they boot up, change state (e.g. group membership, name) and from time to time.

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1800
PROTOCOL: Version 1.0
NTS: ssdp-alive
DeviceName: ZIPPTWOGREY_xxx
DeviceID: 0123456789ab
DeviceState: F,S,P
PORT: 7777
ZoneID: LINK ZippMiniOne_xxx
Creator:
IPAddr: 192.168.178.75
ColorCode: 2010
FWVersion: 1508;4450,;400,
StereoPairID:
ProVersion: 3.0,3.0
DeviceType: 310
AiMicStatus: 1
Characterstic: 2,100,0,15,7,0,DE,de
WifiInfo: ,-59,51/70 ,-62,48/70
VolSync: N
NTS

Notification sub type:

ssdp-alive

The speaker is online

ssdp-byebye

The speaker leaves the network. Clients also send a byebye when they shut down (NTS:ssdp-byebye).

ssdp-sta_init

Handled by the app (UdpMonitor); meaning not determined.

DeviceName

Name of the speaker. Contains the serial number after the model name, e.g. Kitchen_9999-H0020000-07-12345.

DeviceID

12 hex characters, the MAC address of the speaker.

DeviceState

Comma separated state flags. First letter is the group state:

F

Free: not part of a multi-room group

M

Master of a group

S

Slave in a group

G / O

Other group-related states handled by the app (GGROUP)

The meaning of the following letters (S, P in the example) is not documented.

PORT

UDP command port, always 7777.

ZoneID

Name of the group the speaker belongs to, e.g. LINK Name_xxx. Empty when not in a group.

Creator

Group creator. Usually empty.

IPAddr

IP address of the speaker.

ColorCode

Color of the speaker, see Speaker color.

FWVersion

Firmware version. First number is the version (e.g. 809, 879), the numbers after the semicolons are further component versions.

StereoPairID

ID of the stereo pair, empty when not paired.

ProVersion

Protocol version, e.g. 3.0,3.0.

DeviceType

Numeric device type. Not present on all firmwares.

AiMicStatus, Characterstic, WifiInfo, VolSync

Seen on newer speakers (Zipp 2, Zipp Mini 2). WifiInfo contains bssid,rssi,quality/max triples.

VENDOR

Handled by the app when present.

Notifications

Whenever something changes on the speaker (volume knob turned, track changed, battery level, power state, ...), it sends a packet to port 3333 of the controlling host(s). The packet has the Packet format and carries the command ID and the new data like the answer to a fetch would.

The speaker expects an acknowledgement. Without it, it repeats the notification.

Acknowledgement

Send a packet without data to port 3334 of the speaker:

Python

command=0, type 2: aa aa 02 00 00 00 xx xx 00 00

Rust

command=2, type copied from the notification

Both variants are reported to work.

Registration

The speaker sends its notifications to hosts that have recently talked to it. The Android app starts its ServerThread on 3333 and sends commands to port 7777, nothing else is needed.

The libratone-rs code contains a hello packet (set, command 3, data <our ip>,3333) that registers the notification receiver explicitly, but it is not used by the UI. The Python implementation sends a fetch of the charging status (1284) after binding the sockets to "kick" the speaker into sending the notifications.

Notifications seen in practice

Command ID

Name

Notes

14

Power mode

Power mode

15

Standby timer

Standby timer

51

Play status

Play status

64

Volume

Volume

65

Firmware update

Firmware update

103

Device / group state

Device and group state

278

Player info

Player info

258

Battery level

Battery level

1284

Charging state

Charging state

Commands

Playback

Play control (40)

Source: verified

Control playback.

Command ID

40

Type

set

Data

One of the ASCII strings:

PLAY

Start or resume playback

PAUSE

Pause

STOP

Stop (also pauses radio streams)

NEXT

Next track

PREV

Previous track

TOGGL

Toggle play/pause (sic, 5 characters)

MUTE

Mute

UNMUTE

Unmute

Example, PLAY:

aa aa 02 00 28 00 12 34 00 04 50 4c 41 59

When the speaker is in standby, a play command wakes it up (the Loxone wiki uses a favorite preset for that).

Play status (51)

Source: verified

Command ID

51 (fetch, reply and notification)

Data in reply

A single ASCII digit:

0

Playing

1

Stopped

2

Paused

3

Next (transient)

4

Previous (transient)

5

Toggle (transient)

6

Muted

7

Unmuted

Volume (64)

Source: verified

Command ID

64

Type

fetch, set, reply, notification

Data

Volume as number between 0 and 100.

The volume is set per speaker, even when speakers are in a multi-room group, unless volume sync is enabled, see Volume sync.

Example, set to 100 %:

aa aa 02 00 40 00 12 34 00 03 31 30 30

Maximum volume (300)

Source: app

Command ID

300

Type

fetch, set

Data

Maximum volume limit as number.

Mute status (520)

Source: verified (fetch)

Command ID

520

Type

fetch

Data in reply

MUTE or UNMUTE.

Use Play control with MUTE/UNMUTE to change it.

Player info (277, 278)

Source: verified

Information about what is currently playing, and the command to start playing something.

Command ID

278 for fetch, reply and notification

277 for set

Data

JSON object:

isFromChannel

bool. true when the current stream was started from a preset

play_identity

Identifier, e.g. the preset number as string ("1") or the Spotify URI

play_title

Title or station name

play_subtitle

Artist or further description

play_album, play_album_uri, play_artist, play_pic

Metadata when available (picture is an URL)

play_type

Source type: channel (preset), vtuner, spotify, deezer, tidal, napster, douban, xmly, ...

play_token

Token for the service, can be empty

play_username, play_attribution, play_object

Further optional fields

play_preset_available

1 when the item can be stored as preset

Example reply:

{"isFromChannel":false,"play_identity":"1","play_subtitle":"1","play_title":"channel","play_type":"channel","token":""}

Play info is also used to start playback of preset n (this is what the Loxone template does):

{"isFromChannel":false,"play_identity":"1","play_subtitle":"1","play_title":"channel","play_type":"channel","token":""}

sent with set command 277: the speaker starts the radio station stored as preset 1 and wakes up from standby if needed. Valid play_identity values for the presets are "1" to "5". See also Presets.

Clear play info (298)

Source: app

Command ID

298

Type

set with empty data

Play mode (1541)

Source: app

Command ID

1541

Type

fetch, set

Data

String such as repeat/shuffle mode. Used for USB playback.

Play position (USB): 1536, 1537, 1538

Source: app

1536

Current USB play ID (fetch, set)

1537

USB play mode (fetch, set)

1538

USB song info (fetch)

Source selection

Source: app

10

Source info (fetch)

152

Current source (fetch)

121

Switch source (set)

123

Available output sources (fetch)

128

Current output source (fetch)

122

Set output source (set). Values: OUTPUT_WAY_BLUETOOTH (0), OUTPUT_WAY_LOCAL (1), OUTPUT_WAY_AUX (2), OUTPUT_WAY_HDMI (3), OUTPUT_WAY_WIFI_SLAVE (4)

1542

Source switch (set)

537

Switch to WiFi line-in (set with empty data)

Power

Power mode (14)

Source: verified

Command ID

14

Type

fetch, reply, notification

Data in reply

First byte is an ASCII character:

0

Awake

2

Sleeping (standby)

Standby and standby timer (15)

Source: verified

The same command ID is used to put the speaker to sleep, wake it up, and to configure a timer that puts it to sleep.

Command ID

15

Type

set, fetch, notification

Data

00

Wake up from standby

20

Go to standby immediately ("2" + "0" seconds)

2n

Go to standby after n seconds, e.g. 2900 for 15 minutes, 21800 for 30 minutes

F0

Cancel a running timer

libratone-rs uses 02 to sleep and 00 to wake up. The Python library and the Loxone wiki use 20 and 00.

Reply to fetch

Binary, not ASCII: byte 0 0x32 ("2") and a little endian 16 bit number of seconds in bytes 1 and 2 when a timer is set. 0xFF as first byte (or no data) means no timer is set. This is the configured duration, not the remaining time.

Example, go to standby in 15 minutes:

aa aa 02 00 0f 00 12 34 00 04 32 39 30 30

The app also calls the command setPowerMode (data "0") to wake the speaker up and setOffTime.

Sound and voicing

Voicing (516, 518, 524)

Source: verified

A "voicing" is an equalizer profile ("Tone" in the Loxone wiki).

Command ID

516 fetch current voicing

518 set voicing

524 fetch list of all voicings

Data

voicingId for 516 and 518, JSON list for 524.

Voicings known from the app (assets voicings/voicings.json):

ID

Name

Description

V100

Neutral

Basic neutral setting

V101

Easy Listening

Easy and smooth, leaned back sound

V102

Soft and Comfortable

Soft midrange for compressed recordings

V103

Rock the house

Extra drum kick, smooth midrange

V104

Jazz club

Open acoustic sound, focus on voices

V105

Movie time

Extra action and movie re-equalization

V106

Live concert

When the music is loud and dynamic

V107

Classical

Enjoy grand pianos when they are best

V108

Speech

For TV programs with subtle voices

Reply to 524:

[{"description":"Basic neutral setting","name":"Neutral","voicingId":"V100"}, ...]

Example, set "Neutral":

aa aa 02 02 06 00 12 34 00 04 56 31 30 30

Room correction / FullRoom (517, 519, 525)

Source: verified

FullRoom(tm) adapts the sound to the position of the speaker (corner, wall, free-standing, ...).

Command ID

517 fetch current room setting

519 set room setting

525 fetch list of all room settings

Data

Room ID like neutral or outdoor (lower case) or similar, and JSON list for 525:

[{"description":"Basic neutral setting","name":"Neutral","voicingId":"neutral"}, ...]

Known IDs from the Loxone wiki: neutral, outdoor. Always fetch 525 to get the full list for your speaker.

545

Room correction on/off (fetch, set)

Volume sync (506)

Source: app

Command ID

506

Type

fetch, set

Data

bool. When 1, the volume of all speakers in a group is changed together.

Stereo type (512, 515)

Source: app

512

Set stereo type (set), for stereo pairs

515

Fetch speaker stereo type

Presets (favorites)

The speaker has five preset buttons that store a radio station or a streaming service item.

Fetch presets (275)

Source: verified

Command ID

275

Type

fetch

Data in reply

JSON list of channel objects:

[
  {
    "channel_id": 1,
    "channel_identity": "31375",
    "channel_name": "Radio Meuh",
    "channel_type": "vtuner",
    "isPlaying": false,
    "play_token": ""
  }
]
channel_id

Preset slot, 1 to 5

channel_type

vtuner, xmly, doubanfm, spotify, kaishu, deezer, tidal, napster

channel_identity

Station/item ID at the service

channel_name

Display name

station_url, picture_url, username, password, play_token

Optional fields

Store a preset (276)

Source: verified (libratone-rs, app)

Command ID

276

Type

set

Data

One channel object as shown above, JSON encoded.

Play a preset

Use set on command 277 with the preset slot, see Player info.

Multi-room

Speakers can be linked in the app by drag and drop. All speakers of such a "link" (also called zone, group or SoundSpace Link) play the same stream. The group membership is stored on the speakers. The group is identified by a ZoneID like LINK ZippMiniOne_xxx.

Behavior:

  • A stream change (preset, play, pause) on any speaker is mirrored to the other speakers.

  • The volume stays per speaker (unless Volume sync is on).

  • Switching a speaker off does not remove it from the group. When switched on again it re-joins and starts the stream.

Device and group state (103)

Source: verified (notification)

Command ID

103

Type

fetch, notification

Data

ASCII text, e.g. MASTER,LINK <link id>, SLAVE,LINK <link id>, GROUPED,LINK <link id>. Strings containing UNGROUP or UNLINK mean the speaker left the group. Some captures show a leading : or = that should be stripped.

Join group (502)

Source: verified

Command ID

502 (0x01F6)

Type

set

Data

LINK <link id>

Leave group (503)

Source: verified

Command ID

503 (0x01F7)

Type

set

Data

LINK <link id>

Join group as slave (1793)

Source: app

Command ID

1793

Type

set

Data

Zone ID. Used for the "slave only" join of newer firmware.

Device info

Firmware version (5)

Source: verified

Command ID

5

Type

fetch

Data in reply

Firmware version string.

Speaker name (90)

Source: verified

Command ID

90

Type

fetch, set

Data

UTF-8 name of the speaker, e.g. Kitchen.

Setting the name also changes the DeviceName in the Discovery announcements. (The app uses command 816 in addition for some speaker families.)

Serial number (769)

Source: verified

Command ID

769

Type

fetch

Data in reply

Serial number, e.g. 1234-A1234567-12-12345.

Speaker color (770, 771)

Source: verified

Command ID

770 fetch, 771 set

Data

Four digit color code. Known codes from the app:

1001

Pepper Black

1002

Salty Grey

1003

Steel Blue

1004

Raspberry Red

1005

Almond Brown

1006

Icy Blue

1007

Pineapple Yellow

1008

Petrol Blue

1009

Plum Purple

2001

Cloudy Grey

2002

Graphite Grey

2003

Victory Red

2004

Deep Lagoon

2005

Sangria

2006

Atlantic Deep

2007

Signal

2008

Nude

2009

Pastel Blue

9000

Black

9001

White

(Some codes have _OLD variants with different shades in the app.)

Battery level (256, 257, 258)

Source: verified

Command ID

256 fetch

257 reply

258 notification

The reply to the fetch for 256 may come with command ID 256 and empty data first; the real value follows as 257.

Data

Battery level in percent as number, e.g. 59.

Charging state (1284)

Source: verified

Command ID

1284

Type

fetch, reply, notification

Data

0

Discharging (running on battery)

1

Plugged in, charging

2

Plugged in, fully charged

3

Plugged in, not charging

Signal strength (529)

Source: verified

Command ID

529

Type

fetch

Data in reply

Example -86,-42,5/5: noise and signal level in dBm, and quality as current/max.

Capabilities (281)

Source: verified (libratone-rs)

Command ID

281

Type

fetch

Data in reply

JSON:

{"capabilities":[{"name":"vtuner"},{"name":"spotify"}, ...]}

List of streaming services supported by the speaker. (fetchMusicServiceCapability in the app.)

Limited function list (304)

Source: app

Command ID

304

Type

fetch

Data in reply

Three bytes, meaning unknown.

Firmware update (65)

Source: verified (libratone-rs), app

Command ID

65

Type

set, notification

Data

The app builds an UpdateFirmwareCommand with an action number (checks update status 0, checks update version 2, downloads new version 3, starts update 4). The speaker reports its progress as notifications.

530

OTA auto download: AUTO or MANUAL (fetch, set)

Wi-Fi credentials

Source: app

The app has a setWiFiCredentials method that sends the network configuration with a command; the command ID and format have not been analyzed. See Setup mode for the HTTP based way used by the setup mode.

LED

LED always on (613)

Source: app, Loxone wiki

Command ID

613

Type

fetch, set

Data

bool. 0 = LED always on, 1 = LED turns off a few seconds after the last interaction (and lights up briefly on button presses).

Loxone wiki example, "always on":

00 00 02 02 65 00 00 00 00 01 30

LED brightness (301)

Source: app, Loxone wiki

Command ID

301

Type

fetch, set

Data

Brightness in percent, 0 to 100.

LED level (296)

Source: app

Command ID

296

Type

fetch, set

Data

LED display level as string (older firmware).

Voice prompt

Source: app

293

Voice prompt on/off (bool)

294

Voice prompt language

299

List of supported voice prompt languages (fetch)

Other commands

Command IDs found in the Android app (LSSDPNode) that are not covered by the other implementations. Whether they work on a Zipp 1 is unknown; most are meant for newer speakers or headphones.

Names are the app method names. Type F is a fetch, S a set request.

Command ID

Type

Meaning

66

F, S

Delta N firmware update status / start update

274

S

Secret

283

S

Smart EQ

289

F, S

Device user info (fetchDeviceUserInfo) / gesture behavior

290

S

Set current user as owner

291

S

Remove user info

297

S

Update user info

787

F

Alexa (AVS) notification

788

F, S

Alexa (AVS) language

1025

F

Alexa (AVS) voice recognition status

1031

F, S

AI microphone status (AiMicStatus)

1285

F, S

Private mode

2048

S

Alarm operation

2304

F

AI service capability

2305

F

AI service status

2306

S

AI service metadata

2312

S

Alexa auth data

860, 861

F, S

Trial status

4102 to 4108

F, S

Bluetooth device discovery, connect, disconnect, unpair

Newer speakers with a negative ID (e.g. -250 for ANC level, -248 ANC status, -255/-254 LED always on) exist too, and are listed in the app's LSSDPNode class.

HTTP

HTTP services

HTTP is not used for playback control. The following HTTP endpoints exist.

Status page

Source: Loxone wiki

GET http://<speaker>/cgi?Overview

HTML page with status information of the speaker.

UPnP description

Source: Loxone wiki

GET http://<speaker>:49152/description.xml

Standard UPnP device description (XML).

Other /cgi? URLs

The app also contains the strings /cgi?GetList, /cgi?PlayList, /cgi?ResetPlayer, /cgi?Update and /cgi?UploadFile. Their parameters and behavior are unknown.

Setup mode and firmware flashing

Source: app (com.libratone.v3.net.WebPageOperations)

A speaker that is not configured, or is in setup mode, opens its own access point. The app (and UdpMonitor) then talks to the speaker at 192.168.6.1. The web server uses goform handlers.

GET http://<speaker>/index.asp

Returns a page with a JavaScript variable aspgetstringval, which contains the list of Wi-Fi networks seen by the speaker and the current settings.

POST http://<speaker>/goform/formHandlerConfigureProfileSettingsAndDmpName

Configure the Wi-Fi network and/or the speaker name. Content-Type: application/x-www-form-urlencoded.

DMPName

Speaker name

ProfileName

Profile1

SSID

Network name

DHCPClient

DHCP enabled flag

Passphrase

WPA/WPA2 password

KeyIndex, Key0 to Key3

WEP keys

The security type is sent as a text value of the form (NONE), (WEP), (WPA ...) or (WPA2 ...); the name of the form field has not been determined.

When only the name is changed, only DMPName is sent.

GET http://<speaker>/bl_index.asp

Bootloader page, used to update the firmware of old speakers (Live, Lounge).

POST http://<speaker>/goform/formPostHandler

Multipart upload of firmware, field appFirmware, file name apsw.bcd.

POST http://<speaker>/goform/formDownloadConfirmationHandler?button=++++++Ok++++++

Confirm the firmware upload.

GET http://<speaker>/1000/firmware_update/bl_flash_burn_status.asp

Flash progress.

Examples

Examples

Python

A minimal client that sets the volume and reads the speaker name:

import socket, struct

SPEAKER = '192.168.178.75'

def packet(cmd, data=b'', typ=2):
    return struct.pack('>HBHBHH', 0xaaaa, typ, cmd, 0, 0x1234, len(data)) + data

# bind first: replies always go to port 7778
rx = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
rx.bind(('', 7778))
rx.settimeout(2)

tx = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
tx.sendto(packet(64, b'30'), (SPEAKER, 7777))    # set volume 30
tx.sendto(packet(90, typ=1), (SPEAKER, 7777))    # fetch name

data, addr = rx.recvfrom(4096)
cmd = struct.unpack('>H', data[3:5])[0]
payload = data[10:]
print(addr, cmd, payload)

Shell

Using socat:

# play
printf '\xaa\xaa\x02\x00\x28\x00\x12\x34\x00\x04PLAY' | socat - UDP:192.168.178.75:7777

# discovery
printf 'M-SEARCH * HTTP/1.1' | socat - UDP4-DATAGRAM:239.255.255.250:1800,so-broadcast

About

Sources of this documentation:

Each command lists where it is known from:

verified

Used by at least one of the independent implementations, which have been tested against real Zipp 1 speakers (firmware 809, 879).

app

Only seen in the Android app code. The command ID and data format are taken from the app, but it has not been confirmed on a Zipp 1.

The app supports many more speaker families (Air, Citi, Gen, ...) that speak the same protocol. Not every command makes sense on a Zipp.

Last update: 2026-11-12T17:17:15+01:00

License

It is licensed under the GNU Free Documentation License.

Building

You need to install rst2html5 before:

$ pip install rst2html5-tools

or on Debian:

$ apt install docutils-common

Rendering the docs is done via a build script:

$ ./build.sh