Skip to content

Instantly share code, notes, and snippets.

@probonopd
Last active October 12, 2024 18:20
Show Gist options
  • Save probonopd/a93f65560de35ebba095f7c97a68db54 to your computer and use it in GitHub Desktop.
Save probonopd/a93f65560de35ebba095f7c97a68db54 to your computer and use it in GitHub Desktop.
Polycom CX300 Linux HID

Polycom CX300 under Linux

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).

polycom

Questions

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!

lsusb

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)

usbhid-dump

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                                      */

hidraw

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 

USB Protocol

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.

Reading keys

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

Writing to the display and LEDs

To write to the display and LEDs,

  • Connect to the USB device with vendor ID 0x95D and product ID 0x9201
  • 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

Python script to read key presses

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()

Linux phone

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)

Writing to the display, setting the built-in LEDs, etc.

@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!

@probonopd
Copy link
Author

Again, thank you very much @bsmulders.

@dreirund
Copy link

Dear @bsmulders,

I got the display working. Might be interesting for you: https://github.com/bsmulders/CX300Control

I see that https://github.com/bsmulders/CX300Control is gone (HTTP error 404 "Not Found").

Do you have this information/ code anywere else?

Regards!

@bonelifer
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment