The Polycom CX300 and similar Plantronics Calisto 540 (P540-M), snom UC600 are originally optimized for Microsoft Lync (Skype for Business) for Windows and Mac but can also be used with Linux. It shows up as a USB audio playback and recording device (this part works immediately out of the box) plus some USB HID (this needs to be made work; see below).
Please use the comments function below if you know the answer to any of the questions
- Q: How can we make the keyboard regognized under Linux, so that if I press the "1" key, the character
1
is entered as if it was typed on a regular PC keyboard? A: The phone's keyboard does not show up as a HID keyboard. So I am using a Python script (see below). - Q: How can I connect the phone hardware with a SIP client such as baresip or PJSIP with minimal additional effort and overhead? Using the baresip
evdev
plugin? (How to make the device appear in/dev/input/
?) A: The phone does not show up in/dev/input/
. So I am using a Python script (see below). - Q: How can I write to the display on the CX300? A: a reader made it work; see in the comments below. Awesome!
- Q: How can I play the ringing sound on the speakerphone? A: Probably just play a sound to the "sound card" of the phone.
- Q: What commands does Lync send to the phone? (Sniff USB traffic on Lync for Mac with Wireshark) A: a reader has done it; see in the comments below. Awesome!
sudo lsusb -v 095d:9201
Bus 006 Device 002: ID 095d:9201 Polycom, Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x095d Polycom, Inc.
idProduct 0x9201
bcdDevice 1.20
iManufacturer 1 Polycom, Inc.
iProduct 2 Polycom CX300
iSerial 3 ...........
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 206
bNumInterfaces 4
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 1 Control Device
bInterfaceProtocol 0
iInterface 0
AudioControl Interface Descriptor:
bLength 10
bDescriptorType 36
bDescriptorSubtype 1 (HEADER)
bcdADC 1.00
wTotalLength 52
bInCollection 2
baInterfaceNr( 0) 1
baInterfaceNr( 1) 2
AudioControl Interface Descriptor:
bLength 12
bDescriptorType 36
bDescriptorSubtype 2 (INPUT_TERMINAL)
bTerminalID 1
wTerminalType 0x0502 Telephone
bAssocTerminal 4
bNrChannels 1
wChannelConfig 0x0000
iChannelNames 0
iTerminal 0
AudioControl Interface Descriptor:
bLength 12
bDescriptorType 36
bDescriptorSubtype 2 (INPUT_TERMINAL)
bTerminalID 2
wTerminalType 0x0101 USB Streaming
bAssocTerminal 5
bNrChannels 1
wChannelConfig 0x0000
iChannelNames 0
iTerminal 0
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (OUTPUT_TERMINAL)
bTerminalID 4
wTerminalType 0x0502 Telephone
bAssocTerminal 1
bSourceID 2
iTerminal 0
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (OUTPUT_TERMINAL)
bTerminalID 5
wTerminalType 0x0101 USB Streaming
bAssocTerminal 2
bSourceID 1
iTerminal 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 1
bNumEndpoints 1
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
AudioStreaming Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 1 (AS_GENERAL)
bTerminalLink 2
bDelay 1 frames
wFormatTag 1 PCM
AudioStreaming Interface Descriptor:
bLength 11
bDescriptorType 36
bDescriptorSubtype 2 (FORMAT_TYPE)
bFormatType 1 (FORMAT_TYPE_I)
bNrChannels 1
bSubframeSize 2
bBitResolution 16
bSamFreqType 1 Discrete
tSamFreq[ 0] 16000
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 13
Transfer Type Isochronous
Synch Type Synchronous
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 1
bRefresh 0
bSynchAddress 0
AudioControl Endpoint Descriptor:
bLength 7
bDescriptorType 37
bDescriptorSubtype 1 (EP_GENERAL)
bmAttributes 0x00
bLockDelayUnits 0 Undefined
wLockDelay 0 Undefined
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 1
bNumEndpoints 1
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
AudioStreaming Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 1 (AS_GENERAL)
bTerminalLink 5
bDelay 1 frames
wFormatTag 1 PCM
AudioStreaming Interface Descriptor:
bLength 11
bDescriptorType 36
bDescriptorSubtype 2 (FORMAT_TYPE)
bFormatType 1 (FORMAT_TYPE_I)
bNrChannels 1
bSubframeSize 2
bBitResolution 16
bSamFreqType 1 Discrete
tSamFreq[ 0] 16000
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 13
Transfer Type Isochronous
Synch Type Synchronous
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 1
bRefresh 0
bSynchAddress 0
AudioControl Endpoint Descriptor:
bLength 7
bDescriptorType 37
bDescriptorSubtype 1 (EP_GENERAL)
bmAttributes 0x00
bLockDelayUnits 0 Undefined
wLockDelay 0 Undefined
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 3
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 529
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 8
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x04 EP 4 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 8
Device Status: 0x0000
(Bus Powered)
sudo apt-get install hidrd
sudo usbhid-dump -d095d:9201 | grep -v : | xxd -r -p | hidrd-convert -o code
0x05, 0x0B, /* Usage Page (Telephony), */
0x09, 0x01, /* Usage (01h), */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x02, /* Report ID (2), */
0x05, 0x08, /* Usage Page (LED), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x01, /* Report Count (1), */
0x09, 0x17, /* Usage (17h), */
0x91, 0x02, /* Output (Variable), */
0x95, 0x07, /* Report Count (7), */
0x91, 0x03, /* Output (Constant, Variable), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x01, /* Report ID (1), */
0x05, 0x0B, /* Usage Page (Telephony), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x04, /* Report Count (4), */
0x09, 0x20, /* Usage (20h), */
0x09, 0x21, /* Usage (21h), */
0x09, 0x24, /* Usage (24h), */
0x09, 0x50, /* Usage (50h), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x01, /* Report Count (1), */
0x09, 0x2F, /* Usage (2Fh), */
0x81, 0x06, /* Input (Variable, Relative), */
0x95, 0x01, /* Report Count (1), */
0x09, 0x07, /* Usage (07h), */
0x05, 0x09, /* Usage Page (Button), */
0x09, 0x01, /* Usage (01h), */
0x81, 0x02, /* Input (Variable), */
0x06, 0x99, 0xFF, /* Usage Page (FF99h), */
0x0A, 0x1E, 0xFF, /* Usage (FF1Eh), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x03, /* Input (Constant, Variable), */
0x05, 0x0B, /* Usage Page (Telephony), */
0x09, 0x06, /* Usage (06h), */
0xA1, 0x02, /* Collection (Logical), */
0x19, 0xB0, /* Usage Minimum (B0h), */
0x29, 0xBC, /* Usage Maximum (BCh), */
0x15, 0x01, /* Logical Minimum (1), */
0x25, 0x0D, /* Logical Maximum (13), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x04, /* Report Size (4), */
0x81, 0x00, /* Input, */
0xC0, /* End Collection, */
0x75, 0x04, /* Report Size (4), */
0x81, 0x03, /* Input (Constant, Variable), */
0x06, 0x99, 0xFF, /* Usage Page (FF99h), */
0x09, 0x60, /* Usage (60h), */
0x09, 0x61, /* Usage (61h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x02, /* Report Count (2), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x06, /* Report Count (6), */
0x81, 0x03, /* Input (Constant, Variable), */
0x09, 0x62, /* Usage (62h), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0x85, 0x04, /* Report ID (4), */
0x05, 0x0A, /* Usage Page (Ordinal), */
0xA1, 0x02, /* Collection (Logical), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x07, /* Report Count (7), */
0x09, 0x01, /* Usage (01h), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0x85, 0x03, /* Report ID (3), */
0x05, 0x0A, /* Usage Page (Ordinal), */
0xA1, 0x02, /* Collection (Logical), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x3F, /* Report Count (63), */
0x09, 0x01, /* Usage (01h), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0x85, 0x0A, /* Report ID (10), */
0x06, 0x99, 0xFF, /* Usage Page (FF99h), */
0xA1, 0x02, /* Collection (Logical), */
0x09, 0x63, /* Usage (63h), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x2E, /* Report Count (46), */
0xB1, 0x03, /* Feature (Constant, Variable), */
0xC0, /* End Collection, */
0x85, 0x18, /* Report ID (24), */
0x05, 0x0A, /* Usage Page (Ordinal), */
0xA1, 0x02, /* Collection (Logical), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x21, /* Report Count (33), */
0x09, 0x01, /* Usage (01h), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0xC0, /* End Collection, */
0x06, 0x99, 0xFF, /* Usage Page (FF99h), */
0x09, 0x01, /* Usage (01h), */
0xA1, 0x01, /* Collection (Application), */
0x0A, 0x00, 0xFF, /* Usage (FF00h), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x11, /* Report ID (17), */
0x15, 0x00, /* Logical Minimum (0), */
0x27, 0xFF, 0xFF, 0x00, 0x00, /* Logical Maximum (65535), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x10, /* Report Size (16), */
0x0A, 0x01, 0xFF, /* Usage (FF01h), */
0xB1, 0x03, /* Feature (Constant, Variable), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x95, 0x02, /* Report Count (2), */
0x75, 0x08, /* Report Size (8), */
0x0A, 0x02, 0xFF, /* Usage (FF02h), */
0xB1, 0x03, /* Feature (Constant, Variable), */
0xC0, /* End Collection, */
0x09, 0x20, /* Usage (20h), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x12, /* Report ID (18), */
0x09, 0x35, /* Usage (35h), */
0x09, 0x36, /* Usage (36h), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x95, 0x02, /* Report Count (2), */
0x75, 0x08, /* Report Size (8), */
0xB1, 0x03, /* Feature (Constant, Variable), */
0x19, 0x81, /* Usage Minimum (81h), */
0x29, 0x8A, /* Usage Maximum (8Ah), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x0A, /* Report Count (10), */
0x75, 0x01, /* Report Size (1), */
0xB1, 0x03, /* Feature (Constant, Variable), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x06, /* Report Size (6), */
0xB1, 0x03, /* Feature (Constant, Variable), */
0xC0, /* End Collection, */
0x09, 0x24, /* Usage (24h), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x13, /* Report ID (19), */
0x09, 0x26, /* Usage (26h), */
0x09, 0x25, /* Usage (25h), */
0x0A, 0x10, 0xFF, /* Usage (FF10h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x03, /* Report Count (3), */
0x75, 0x01, /* Report Size (1), */
0x91, 0x02, /* Output (Variable), */
0x0A, 0x11, 0xFF, /* Usage (FF11h), */
0x25, 0x0F, /* Logical Maximum (15), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x04, /* Report Size (4), */
0x91, 0x02, /* Output (Variable), */
0x75, 0x01, /* Report Size (1), */
0x91, 0x03, /* Output (Constant, Variable), */
0xC0, /* End Collection, */
0x09, 0x48, /* Usage (48h), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x14, /* Report ID (20), */
0x19, 0x81, /* Usage Minimum (81h), */
0x29, 0x8A, /* Usage Maximum (8Ah), */
0x15, 0x01, /* Logical Minimum (1), */
0x25, 0x0A, /* Logical Maximum (10), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x04, /* Report Size (4), */
0x91, 0x00, /* Output, */
0x95, 0x01, /* Report Count (1), */
0x75, 0x04, /* Report Size (4), */
0x91, 0x03, /* Output (Constant, Variable), */
0x0A, 0x23, 0xFF, /* Usage (FF23h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x03, /* Logical Maximum (3), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x02, /* Report Size (2), */
0x91, 0x02, /* Output (Variable), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x05, /* Report Size (5), */
0x91, 0x03, /* Output (Constant, Variable), */
0x0A, 0x22, 0xFF, /* Usage (FF22h), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x01, /* Report Size (1), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0x09, 0x2B, /* Usage (2Bh), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x15, /* Report ID (21), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x07, /* Report Size (7), */
0x91, 0x03, /* Output (Constant, Variable), */
0x0A, 0x24, 0xFF, /* Usage (FF24h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x01, /* Report Size (1), */
0x91, 0x02, /* Output (Variable), */
0x0A, 0x2C, 0xFF, /* Usage (FF2Ch), */
0x27, 0xFF, 0xFF, 0x00, 0x00, /* Logical Maximum (65535), */
0x95, 0x08, /* Report Count (8), */
0x75, 0x10, /* Report Size (16), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0x0A, 0x17, 0xFF, /* Usage (FF17h), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x16, /* Report ID (22), */
0x0A, 0x18, 0xFF, /* Usage (FF18h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x0F, /* Logical Maximum (15), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x04, /* Report Size (4), */
0x91, 0x02, /* Output (Variable), */
0x75, 0x04, /* Report Size (4), */
0x91, 0x03, /* Output (Constant, Variable), */
0x0A, 0x1A, 0xFF, /* Usage (FF1Ah), */
0x0A, 0x1B, 0xFF, /* Usage (FF1Bh), */
0x0A, 0x1F, 0xFF, /* Usage (FF1Fh), */
0x0A, 0x20, 0xFF, /* Usage (FF20h), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x04, /* Report Count (4), */
0x75, 0x01, /* Report Size (1), */
0x91, 0x02, /* Output (Variable), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x02, /* Report Size (2), */
0x15, 0xFF, /* Logical Minimum (-1), */
0x25, 0x01, /* Logical Maximum (1), */
0x0A, 0x1C, 0xFF, /* Usage (FF1Ch), */
0x91, 0x26, /* Output (Variable, Relative, No Preferred), */
0x75, 0x02, /* Report Size (2), */
0x91, 0x03, /* Output (Constant, Variable), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x17, /* Report ID (23), */
0x15, 0x00, /* Logical Minimum (0), */
0x27, 0xFF, 0xFF, 0x00, 0x00, /* Logical Maximum (65535), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x10, /* Report Size (16), */
0x09, 0x64, /* Usage (64h), */
0xB1, 0x02, /* Feature (Variable), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x95, 0x02, /* Report Count (2), */
0x75, 0x08, /* Report Size (8), */
0x09, 0x65, /* Usage (65h), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
sudo apt update
sudo apt-get install libudev-dev libusb-1.0-0-dev libfox-1.6-dev autotools-dev autoconf automake libtool git
git clone https://github.com/signal11/hidapi
cd hidapi/
./bootstrap
./configure --enable-testgui --prefix=/usr
make
sudo make install
sudo hidapi-hidraw-testgui
# Redial loudspeaker
Received 8 bytes:
01 04 00 03 00 4e 80 00
Received 8 bytes:
01 00 00 03 00 4e 80 00
# Pause
Received 8 bytes:
01 02 00 03 00 4e 80 00
Received 8 bytes:
01 00 00 03 00 4e 80 00
# Delete
Received 8 bytes:
01 20 00 03 00 4e 80 00
Received 8 bytes:
01 00 00 03 00 4e 80 00
# 1
Received 8 bytes:
01 00 02 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 2
Received 8 bytes:
01 00 03 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 3
Received 8 bytes:
01 00 04 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 4
Received 8 bytes:
01 00 05 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 5
Received 8 bytes:
01 00 06 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 6
Received 8 bytes:
01 00 07 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 7
Received 8 bytes:
01 00 08 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 8
Received 8 bytes:
01 00 09 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 9
Received 8 bytes:
01 00 0a 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# *
Received 8 bytes:
01 00 0b 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# 0
Received 8 bytes:
01 00 01 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# #
Received 8 bytes:
01 00 0c 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# Speaker
Received 8 bytes:
01 01 00 03 51 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# Headphone
Received 8 bytes:
01 01 00 03 60 4e 40 00
Received 8 bytes:
01 00 00 03 00 4e 40 00
# Mute on
Received 8 bytes:
01 10 00 03 01 ff ff 01
Received 8 bytes:
01 00 00 03 01 ff ff 01
# Mute off
Received 8 bytes:
01 10 00 03 01 ff ff 00
Received 8 bytes:
01 00 00 03 01 ff ff 00
# Loudness 1 loudspeaker
Received 8 bytes:
01 00 00 03 00 d1 16 00
# Loudness 2 loudspeaker
Received 8 bytes:
01 00 00 03 00 3b 20 00
# Loudness 3 loudspeaker
Received 8 bytes:
01 00 00 03 00 86 2d 00
# Loudness 4 loudspeaker
Received 8 bytes:
01 00 00 03 00 4e 40 00
# Loudness 5 loudspeaker
Received 8 bytes:
01 00 00 03 00 d5 5a 00
# Loudness 6 loudspeaker
Received 8 bytes:
01 00 00 03 00 4e 80 00
# Loudness 7 loudspeaker
Received 8 bytes:
01 00 00 03 00 3c b5 00
# Loudness 8 loudspeaker
Received 8 bytes:
01 00 00 03 00 ff ff 00
# Loudness 9 loudspeaker
Received 8 bytes:
01 00 00 03 01 ff ff 00
# Loudness 10 loudspeaker
Received 8 bytes:
01 00 00 03 02 ff ff 00
# Off hook
Received 8 bytes:
01 01 00 03 40 4e 80 00
Received 8 bytes:
01 00 00 03 00 4e 80 00
# On hook
Received 8 bytes:
01 00 00 03 02 ff ff 00
The following information was investigated by Bobbie Smulders and documented at https://bsmulders.com/2018/04/polycom-cx300/ which is gratefully acknowledged. It is mirrored here in case the blog should ever go down.
The second and third bytes of the packet are for the button pressed:
KEYPAD_0_DOWN = 00 01
KEYPAD_1_DOWN = 00 02
KEYPAD_2_DOWN = 00 03
KEYPAD_3_DOWN = 00 04
KEYPAD_4_DOWN = 00 05
KEYPAD_5_DOWN = 00 06
KEYPAD_6_DOWN = 00 07
KEYPAD_7_DOWN = 00 08
KEYPAD_8_DOWN = 00 09
KEYPAD_9_DOWN = 00 0A
KEYPAD_STAR_DOWN = 00 0B
KEYPAD_DASH_DOWN = 00 0C
REDIAL_DOWN = 04 00
FLASH_DOWN = 02 00
DELETE_DOWN = 20 00
MUTE_DOWN = 10 00
HOOK_UP = 01 00
NO_KEY = 00 00
The fourth byte is to communicate if the phone is receiving audio:
ENABLED = 00
DISABLED = 03
The fifth byte is to communicate the type of audio device the phone is using:
HANDSET = 40
SPEAKER = 50
HEADSET = 60
NO_CHANGE = 00
SPEAKER_LOUD = 01
SPEAKER_LOUDER = 02
The sixth and seventh byte are to communicate the current volume level:
VOLUME_01 = 70 0B
VOLUME_02 = 27 10
VOLUME_03 = D1 16
VOLUME_04 = 3B 20
VOLUME_05 = 86 2D
VOLUME_06 = 4E 40
VOLUME_07 = D5 5A
VOLUME_08 = 4E 80
VOLUME_09 = 3C B5
VOLUME_10 = FF FF
The eighth byte is to communicate whether or not the microphone is muted:
NOT_MUTED = 00
MUTED = 01
Example:
01 00 06 00 00 4e 40 00
Keypad: 00 06 KEYPAD_5_DOWN
Audio: 00 NO_INPUT
Audio type: 00 NO_CHANGE
Volume: 4e 40 VOLUME_06
Microphone: 00 NOT_MUTED
To write to the display and LEDs,
- Connect to the USB device with vendor ID
0x95D
and product ID0x9201
- Open the device (e.g.,
hid_open_path
) - Send a feature report (e.g.,
hid_send_feature_report
) with the byte array[0x09, 0x04, 0x01, 0x02, 0x17]
(the last byte being the report ID). The first byte of the feature report is for the language, encoded as a Microsoft LCID (Language Code Identifier) (0x09
= English) - Send a message (e.g.,
hid_write
) with the byte array[0x16, 0x01]
Clear display: 13 00
Show text in four corners: 13 0D
Display in the top left corner: 14 01 80
Display in the bottom left corner: 14 02 80
Display in the top right corner: 14 03 80
Display in the bottom right corner: 14 04 80
Show text in two lines: 13 15
Display in the top line: 14 05 80
Display in the bottom line: 14 0A 80
To send data to the display, start the process by sending out a data packet containing which mode you want to use, four corners or two lines. Then send a data packet containing the corner or line you want to write data to. Finally, send the actual message encoded in ASCII. The second byte is for indicating if this message is the last one. Use 0x00
for all messages except the last, that one needs 0x80
so the phone knows when all data is received. Repeat these steps for the other corners or line.
15 00 48 00 65 00 6c 00 6c 00 6f 00 20 00 77 00 6f 00
Hello wo
15 80 72 00 6c 00 64 00 21 00 00 00 00 00 00 00 00 00
rld!
Presence LED on the bottom right of the phone:
STATUS_AVAILABLE = 16 01
STATUS_BUSY = 16 03
STATUS_BE_RIGHT_BACK = 16 05
STATUS_AWAY = 16 05
STATUS_DO_NOT_DISTURB = 16 06
STATUS_OFF_WORK = 16 07
Or when translated to colors:
STATUS_LED_GREEN = 16 01
STATUS_LED_RED = 16 03
STATUS_LED_YELLOW_RED = 16 04
STATUS_LED_YELLOW = 16 05
STATUS_LED_OFF = 16 07
Speakerphone status LED:
OFF = 02 00
ON = 02 01
Improvements welcome!
#!/usr/bin/env python
import sys, time, os
debug = False
combinations = [
[4, "40", "OffHook"],
[4, "50", "Speaker"],
[4, "52", "Speaker"], # Probably not correct
[4, "60", "Headphones"],
[1, "04", "Redial"],
[1, "02", "Hold"],
[1, "10", "Mute"],
[1, "20", "Delete"],
[2, "01", "Key0"],
[2, "02", "Key1"],
[2, "03", "Key2"],
[2, "04", "Key3"],
[2, "05", "Key4"],
[2, "06", "Key5"],
[2, "07", "Key6"],
[2, "08", "Key7"],
[2, "09", "Key8"],
[2, "0a", "Key9"],
[2, "0b", "Star"],
[2, "0c", "Hash"],
]
def interpret(command):
command_string = " ".join("{:02x}".format(ord(c)) for c in command)
if (debug):
print(command_string)
if (command_string.startswith("01 00 00 00 00 ff ff")):
print("OnHook")
i = 0
for bit in command:
for ch in bit:
hv = hex(ord(ch)).replace('0x', '')
if len(hv) == 1:
hv = '0' + hv
if (debug):
print("%i: %s" % (i, hv))
for combination in combinations:
if (i == combination[0] and hv == combination[1]):
print(combination[2])
i = i + 1
def listen():
fp = open('/dev/hidraw0', 'rb')
t = time.time()
command = ""
while True:
buffer = fp.read(8)
if time.time() - t > 0.01:
interpret(command)
command = ""
for c in buffer:
if c != 0:
command = command + c
t = time.time()
if (__name__ == "__main__"):
listen()
The following can run e.g., on an Orange Pi Zero to make a phone using the CX300. It is a rudimentary proof-of-concept.
#!/usr/bin/env python
debug = False
import threading
import time
from flask import Flask # apt install python-flask
import atexit
import pjsua as pj
LOG_LEVEL=5
current_call = None
# For Neopixels
import spidev
import ws2812
cx300_combinations = [
[4, "40", "OffHook"],
[4, "50", "Speaker"],
[4, "52", "Speaker"], # Probably not correct
[4, "60", "Headphones"],
[1, "04", "Redial"],
[1, "02", "Hold"],
[1, "10", "Mute"],
[1, "20", "Delete"],
[2, "01", "Key0"],
[2, "02", "Key1"],
[2, "03", "Key2"],
[2, "04", "Key3"],
[2, "05", "Key4"],
[2, "06", "Key5"],
[2, "07", "Key6"],
[2, "08", "Key7"],
[2, "09", "Key8"],
[2, "0a", "Key9"],
[2, "0b", "Star"],
[2, "0c", "Hash"],
]
app = Flask(__name__)
number_to_be_dialed = []
#
# What should happen when this application quits
#
def shutdown():
print("Shutting down")
neopixels_off()
atexit.register(shutdown)
def alarm(message):
print("ALARM: %s" % (message))
neopixels_red()
def dial(number):
neopixels_off()
if len(number) > 0:
print("Dial number: %s" % (number))
neopixels_yellow()
number_to_be_dialed[:] = [] # Clear
# make_call("sip:" + number + "@sip.easybell.de")
make_call("sip:[email protected]")
else:
print("No number entered yet, play dialtone or say text")
neopixels_green()
#
# What should happen when keys are pressed
#
def key0():
if(current_call):
current_call.dial_dtmf("0")
else:
number_to_be_dialed.append("0")
def key1():
if(current_call):
current_call.dial_dtmf("1")
else:
number_to_be_dialed.append("1")
def key2():
if(current_call):
current_call.dial_dtmf("2")
else:
number_to_be_dialed.append("2")
def key3():
if(current_call):
current_call.dial_dtmf("3")
else:
number_to_be_dialed.append("3")
def key4():
if(current_call):
current_call.dial_dtmf("4")
else:
number_to_be_dialed.append("4")
def key5():
if(current_call):
current_call.dial_dtmf("5")
else:
number_to_be_dialed.append("5")
def key6():
if(current_call):
current_call.dial_dtmf("6")
else:
number_to_be_dialed.append("6")
def key7():
if(current_call):
current_call.dial_dtmf("7")
else:
number_to_be_dialed.append("7")
def key8():
if(current_call):
current_call.dial_dtmf("8")
else:
number_to_be_dialed.append("8")
def key9():
if(current_call):
current_call.dial_dtmf("9")
else:
number_to_be_dialed.append("9")
def star():
if(current_call):
current_call.dial_dtmf("*")
else:
number_to_be_dialed.append("*")
def hash():
if(current_call):
current_call.dial_dtmf("#")
else:
number_to_be_dialed.append("#")
def offhook():
if not current_call:
dial(''.join(map(str, number_to_be_dialed)))
def speaker():
if not current_call:
dial(''.join(map(str, number_to_be_dialed)))
def onhook():
print("ONHOOK!!")
neopixels_off()
if not current_call:
print "There is no call"
else:
current_call.hangup()
def mute():
print("MUTE!!")
neopixels_off()
#
# Neopixel functions
#
def neopixels_off():
ws2812.write2812(spi, [[0,0,0]]*16)
def neopixels_green():
ws2812.write2812(spi, [[5,0,0]]*16)
def neopixels_yellow():
ws2812.write2812(spi, [[5,5,0]]*16)
def neopixels_red():
ws2812.write2812(spi, [[0,5,0]]*16)
def neopixels_blue():
ws2812.write2812(spi, [[0,0,5]]*16)
def neopixels_white():
ws2812.write2812(spi, [[3,3,3]]*16)
#
# Handle key presses on CX300; can write your own similar functions for different devices
#
@app.before_first_request
def cx300_listen_keys():
def run_job():
try:
fp = open('/dev/hidraw0', 'rb')
except:
return
print("Opened /dev/hidraw0 for listening to CX300 commands")
t = time.time()
command = ""
while True:
try:
buffer = fp.read(8)
except:
alarm("Could not read from CX300")
exit()
if time.time() - t > 0.01:
command_string = " ".join("{:02x}".format(ord(c)) for c in command)
if (debug):
print(command_string)
if (command_string.startswith("01 00 00 00 00 ff ff")):
print("OnHook")
onhook()
i = 0
for bit in command:
for ch in bit:
hv = hex(ord(ch)).replace('0x', '')
if len(hv) == 1:
hv = '0' + hv
if (debug):
print("%i: %s" % (i, hv))
for combination in cx300_combinations:
if (i == combination[0] and hv == combination[1]):
print(combination[2])
try:
method = eval(combination[2].lower())
# print("Calling function %s()" % (method))
method()
except:
pass
i = i + 1
command = ""
for c in buffer:
if c != 0:
command = command + c
t = time.time()
thread = threading.Thread(target=run_job)
thread.start()
#
# Web interface
#
@app.route("/")
def hello():
return "<h1>OpenPhone</h1>"
#
# pjsip functions
#
# Callback to receive events from account
class MyAccountCallback(pj.AccountCallback):
sem = None
def __init__(self, account=None):
pj.AccountCallback.__init__(self, account)
# Notification on incoming call
def on_incoming_call(self, call):
global current_call
if current_call:
call.answer(486, "Busy")
return
print "Incoming call from ", call.info().remote_uri
print "Press 'a' to answer"
current_call = call
call_cb = MyCallCallback(current_call)
current_call.set_callback(call_cb)
current_call.answer(180)
def wait(self):
self.sem = threading.Semaphore(0)
self.sem.acquire()
def on_reg_state(self):
if self.sem:
if self.account.info().reg_status >= 200:
self.sem.release()
def log_cb(level, str, len):
print str,
# Callback to receive events from Call
class MyCallCallback(pj.CallCallback):
def __init__(self, call=None):
pj.CallCallback.__init__(self, call)
# Notification when call state has changed
def on_state(self):
global current_call
current_call = self.call
neopixels_white()
print "Call with", self.call.info().remote_uri,
print "is", self.call.info().state_text,
print "last code =", self.call.info().last_code,
print "(" + self.call.info().last_reason + ")"
if self.call.info().state == pj.CallState.DISCONNECTED:
current_call = None
neopixels_off()
print 'Current call is', current_call
# Notification when call's media state has changed.
def on_media_state(self):
if self.call.info().media_state == pj.MediaState.ACTIVE:
# Connect the call to sound device
call_slot = self.call.info().conf_slot
pj.Lib.instance().conf_connect(call_slot, 0)
pj.Lib.instance().conf_connect(0, call_slot)
print "Media is now active"
else:
print "Media is inactive"
# Function to make call
def make_call(uri):
lib.thread_register("python worker") # Otherwise getting
# python: ../src/pj/os_core_unix.c:692: pj_thread_this: Assertion `!"Calling pjlib from unknown/external thread.
# You must " "register external threads with pj_thread_register() " "before calling any pjlib functions."' failed.
# when trying to make a call!
try:
print "Making call to", uri
return acc.make_call(uri, cb=MyCallCallback())
except pj.Error, e:
print "Exception: " + str(e)
return None
#
# Main
#
if __name__ == "__main__":
spi = spidev.SpiDev() # For Neopixels
spi.open(1,0) # For Neopixels
lib = pj.Lib()
try:
lib.init(log_cfg = pj.LogConfig(level=4, callback=log_cb))
# List all sound devices and select one we want
snd_devs = lib.enum_snd_dev()
i = 0
for snd_dev in snd_devs:
print("%i: %s" % (i, snd_dev.name))
if(snd_dev.name.startswith("plughw:CARD=CX300")):
lib.set_snd_dev(i, i)
i = i+1
lib.create_transport(pj.TransportType.UDP, pj.TransportConfig(5080))
lib.start()
####acc = lib.create_account(pj.AccountConfig("...", "...", "..."))
# Create UDP transport which listens to any available port
transport = lib.create_transport(pj.TransportType.UDP,
pj.TransportConfig(0))
print "\nListening on", transport.info().host,
print "port", transport.info().port, "\n"
my_sip_uri = "sip:" + transport.info().host + ":" + str(transport.info().port)
print "My SIP URI is", my_sip_uri
# Create local account
acc = lib.create_account_for_transport(transport, cb=MyAccountCallback())
acc_cb = MyAccountCallback(acc)
acc.set_callback(acc_cb)
###acc_cb.wait()
print "\n"
print "Registration complete, status=", acc.info().reg_status, \
"(" + acc.info().reg_reason + ")"
neopixels_green()
time.sleep(1)
neopixels_off()
except pj.Error, e:
print "Exception: " + str(e)
lib.destroy()
neopixels_red()
cx300_listen_keys()
app.run(host='0.0.0.0', port=80)
@bsmulders has documented the commands needed to write to the display, set the built-in LEDs, etc. at https://bsmulders.com/2018/04/polycom-cx300/. I ported this over to Python. The following results in the LED being lit green and the display saying "Hello world!":
sudo apt -y install python-hid
with the following cx300.py
, running as root (may need an udev rule for non-root users):
import hid, time
try:
h = hid.device()
h.open(0x095d, 0x9201)
print("Manufacturer: %s" % h.get_manufacturer_string())
print("Product: %s" % h.get_product_string())
print("Serial No: %s" % h.get_serial_number_string())
h.set_nonblocking(1)
# Send feature report
h.send_feature_report([0x17, 0x09, 0x04, 0x01, 0x02])
time.sleep(0.05)
# STATUS_LED_GREEN
h.write([0x16, 0x01])
time.sleep(0.05)
# Clear display
h.write([0x13, 0x00])
time.sleep(0.05)
# Show text in two lines
h.write([0x13, 0x15])
time.sleep(0.05)
# Display in the top line
h.write([0x14, 0x05, 0x80])
time.sleep(0.05)
# Hello wo
h.write([0x15, 0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x77, 0x00, 0x6f, 0x00])
time.sleep(0.05)
# rld!
h.write([0x15, 0x80, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
time.sleep(0.05)
print("Closing the device")
h.close()
except IOError as ex:
print(ex)
Works!
@dreirund here's a remaining fork:
https://github.com/OE4AMW/cx300-control