Skip to content

Instantly share code, notes, and snippets.

@ca0abinary
Created August 12, 2022 13:00
Show Gist options
  • Select an option

  • Save ca0abinary/ee99d74ace8c5290853af911c2bc335b to your computer and use it in GitHub Desktop.

Select an option

Save ca0abinary/ee99d74ace8c5290853af911c2bc335b to your computer and use it in GitHub Desktop.
Polycom MPTZ Serial Connection Reverse Engineering Notes

Polycom Eagle Eye Protocol RE

MPTZ-6

Serial connection at 9600 8-E-1

Device issues e0 until initialized with 81 40. Initialization code can be sent at any time.

import serial
s = serial.Serial('/dev/ttyUSB0',9600,parity=serial.PARITY_EVEN)
s.write(b'\x81\x40')            # init (expect a0 93 40 01 10)
s.write(b'\x82\x06\x77')        # get fw info
# these commands just expect an ok response (a0 92 40 00)
s.write(b'\x84\x43\x02\x04\x55')# set zoom 119.5 deg fov
s.write(b'\x8a\x41\x50\x3a\x03\x51\x00\x5a\x7f\x7f\x00') # set position P=97.7 T=21.8

PTZ

84 43 02 09 2b                      # zoom to 119.5 deg fov
8a 41 50 3a 02 0c 00 5a 7f 7f 00    # set position 400 200 64
                                    # API 652 218 1195
                                    # P 65.2 degrees T 21.8 degrees
                                    # Hex to Dec PTZ
                                    # 524 90 127/32639

84 43 02 09 2b                      # zoom to 119.5 deg fov
8a 41 50 3a 03 51 00 5a 7f 7f 00    # set position 600 200 64
                                    # API 977 218 1195
                                    # P 97.7 degrees T 21.8 degrees
                                    # Hex to Dec PTZ
                                    # 849 90 127/32639

8a 41 50 32 03 51 00 6d 7f 7f 00    # set position 600 100 64
                                    # API 977 109 1195
                                    # P 97.7 T 10.9
                                    # 6d = 109

84 43 02 04 55                      # zoom (84 43 02)
                                    # 092b/2347 = 64 / 119.5 deg fov
                                    # 0455/1109 = 32 /  59.7 deg fov
                                    # 022a/554  = 16 /  29.8 deg fov
8a 41 50 32 03 51 00 6d 7f 7f 00    # set position 600 100 32

Breakdown
    Send wake up                    84 43 02 09 2b
    Expect ok                       a0 92 40 00

    Move to                         8a 41 50 3a
    16 bit pan                      degrees * 10 - 128 (97.7 = 977-128 = 849 = 03 51)
    16 bit tilt                     degrees * 10 (10.9 * 10 = 109)
    ???                             7f 7f 00
@ca0abinary
Copy link
Author

ca0abinary commented Aug 15, 2022

Building an individual move command

84    # start
43    # move
XX    # move how? 02 = zoom, 04 = pan, 05 = tilt
XX    # MSB (big stepping)
      # Pan  00 = leftmost - 0f = rightmost (from camera's perspective)
      # Tilt 01 = down 02 = up
      # Zoom 01 = near - 11 = far
XX    # LSB (little stepping)
      # Pan  00 - cf
      # Tilt 00 - ff
      # Zoom 00 - ff

This might be good for an Arduino hooked up with tactile switches for a super simple control scheme. It would be easy to control using only the MSB since the stepping is in the goldilocks zone. Sending a command outside the range is simply ignored.

Because pan centering is best at c0 if using a simple Arduino control setting the LSB to c0 for pan is a good idea. Fine control might be achieved with a rotary encoder?

Examples

84 43 02 00 00 # Zoom to nearest
84 43 04 06 c0 # Pan to center
84 43 05 02 00 # Tilt to center

@ca0abinary
Copy link
Author

Simple Arduino Sketch using an LCD display, RS232 level converter, and rotary switch to control an MPTZ-6 or MPTZ-9 standalone.
PXL_20220826_143510516

demo.mp4
// MPTZ Controller
// Display: 2 row 16 column HD44780 with PCF8574 I2C controller
// Input  : Rotary encoder (CLK, DI, SW, VCC, GND)
// Camera : Serial (9600 8-E-1)
// Tested with MPTZ-6
#include <Arduino.h>
#include <HD44780_LCD_PCF8574.h>
#include <EncoderTool.h>
// https://github.com/ljbeng/SoftwareSerialParity
#include <SoftwareSerialParity.h>

using namespace EncoderTool;

// Defines
// LCD A4 = SCL, A5 = SDA
#define LCD_ROWS        2
#define LCD_COLS        16
#define LCD_I2C_ADDRESS 0x27
#define ROTARY_PIN_A    PIN_A1
#define ROTARY_PIN_B    PIN_A2
#define ROTARY_BUTTON   PIN_A3
#define SERIAL_RX       2
#define SERIAL_TX       3
#define MAX_STATE       5
#define MPTZ_PAN        0x04
#define MPTZ_TILT       0x05
#define MPTZ_ZOOM       0x02

// Devices
PolledEncoder rotary;
SoftwareSerialParity serial(SERIAL_RX, SERIAL_TX);
HD44780LCD lcd(LCD_ROWS, LCD_COLS, LCD_I2C_ADDRESS);

// Globals
byte pan  = 0x06, pan_step  = 0xC0;
byte tilt = 0x02, tilt_step = 0x00;
byte zoom = 0x08, zoom_step = 0x00;
byte state = 0, state_changed = 0, value_changed = 0;

void setup() {
  // Init software serial port to 9600 8-E-1
  serial.begin(9600, EVEN);

  // Init rotary encoder
  rotary.begin(ROTARY_PIN_A, ROTARY_PIN_B, ROTARY_BUTTON);
  rotary.setCountMode(CountMode::halfAlt);

  // Init 16x2 LCD
  lcd.PCF8574_LCDInit(LCDCursorTypeOnBlink);
  lcd.PCF8574_LCDClearScreen();
  lcd.PCF8574_LCDBackLightSet(true);

  wait_for_camera();

  // Init to default state
  mptz_center();
  process_state();
  draw();
}

void loop() {
  value_changed = 0;
  state_changed = 0;

  rotary.tick();

  if (rotary.valueChanged()) {
    value_changed = 1;
  }

  if (rotary.buttonChanged() && rotary.getButton() == 0) {
    state++;
    if (state > MAX_STATE) {
      state = 0;
    }
    state_changed = 1;
  }

  if (value_changed == 1 || state_changed == 1) {
    process_state();
    draw();
  }
}

void draw() {
  lcd.PCF8574_LCDResetScreen(LCDCursorTypeOnBlink);

  // Draw static elements
  lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 0);
  lcd.print("Pan : ");
  lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 12);
  lcd.print("Z:");
  lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 0);
  lcd.print("Tilt: ");
  lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 12);
  lcd.print("Z:");

  // Pan
  lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 6);
  PrintHex8(pan);
  lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 1);
  PrintHex8(pan_step);

  // Tilt
  lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 6);
  PrintHex8(tilt);
  lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 1);
  PrintHex8(tilt_step);

  // Zoom
  lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 14);
  PrintHex8(zoom);
  lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 14);
  PrintHex8(zoom_step);

  // Set cursor position
  switch (state) {
    case 0: // Pan Step
      lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 0);
      lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 7);
      break;
    case 1: // Pan Sub-step
      lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 0);
      lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 10);
      break;
    case 2: // Tilt Step
      lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 0);
      lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 7);
      break;
    case 3: // Tilt Sub-step
      lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 0);
      lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 10);
      break;
    case 4: // Zoom Step
      lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 0);
      lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 15);
      break;
    case 5: // Zoom Sub-step
      lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 0);
      lcd.PCF8574_LCDMoveCursor(LCDMoveRight, 15);
      break;
  }
}

void PrintHex8(uint8_t data)
{
  char tmp[16];
  sprintf(tmp, "%.2X", data); 
  lcd.print(tmp);
}

void process_state() {
  byte rotary_value = rotary.getValue();
  switch (state) {
    // Pan Step
    case 0:
      if (state_changed == 1) {
        rotary.setLimits(0x00, 0x0f);
        rotary.setValue(pan);
      } else {
        pan = rotary_value;
        mptz_move(MPTZ_PAN, pan, pan_step);
      }
      break;
    // Pan Sub-step
    case 1:
      if (state_changed == 1) {
        rotary.setLimits(0x00, 0xcf);
        rotary.setValue(pan_step);
      } else {
        pan_step = rotary_value;
        mptz_move(MPTZ_PAN, pan, pan_step);
      }
      break;
    // Tilt Step
    case 2:
      if (state_changed == 1) {
        rotary.setLimits(0x01, 0x02);
        rotary.setValue(tilt);
      } else {
        tilt = rotary_value;
        mptz_move(MPTZ_TILT, tilt, tilt_step);
      }
      break;
    // Tilt Sub-step
    case 3:
      if (state_changed == 1) {
        rotary.setLimits(0x00, 0xff);
        rotary.setValue(tilt_step);
      } else {
        tilt_step = rotary_value;
        mptz_move(MPTZ_TILT, tilt, tilt_step);
      }
      break;
    // Zoom Step
    case 4:
      if (state_changed == 1) {
        rotary.setLimits(0x01, 0x11);
        rotary.setValue(zoom);
      } else {
        zoom = rotary_value;
        mptz_move(MPTZ_ZOOM, zoom, zoom_step);
      }
      break;
    // Zoom Sub-step
    case 5:
      if (state_changed == 1) {
        rotary.setLimits(0x00, 0xff);
        rotary.setValue(zoom_step);
      } else {
        zoom_step = rotary_value;
        mptz_move(MPTZ_ZOOM, zoom, zoom_step);
      }
      break;
  }
}

void wait_for_camera() {
  lcd.PCF8574_LCDClearScreen();
  lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 0);
  lcd.print("Wait for cam");
  lcd.PCF8574_LCDGOTO(LCDLineNumberTwo, 0);
  lcd.print("Click to break");
  lcd.PCF8574_LCDGOTO(LCDLineNumberOne, 14);

  // Clear buffer
  while (serial.available() > 0) {
    serial.read();
  }

  // Wait for camera to signal
  while (true) {
    rotary.tick();

    if (serial.available() > 0) {
      byte b = serial.read();
      PrintHex8(b);
      lcd.PCF8574_LCDMoveCursor(LCDMoveLeft, 2);
      if (b == 0xe0) {
        mptz_init();
        break;
      } 
    } else if (rotary.buttonChanged() && rotary.getButton() == 0) {
      state_changed = 1;
      break;
    }
  }
}

void mptz_init() {
  serial.write(0x81);
  serial.write(0x40);
  serial.flush();
  delay(100);
}

void mptz_center() {
  mptz_move(MPTZ_PAN, pan, pan_step);
  mptz_move(MPTZ_TILT, tilt, tilt_step);
  mptz_move(MPTZ_ZOOM, zoom, zoom_step);
}

void mptz_move(byte cmd, byte msb, byte lsb) {
  serial.write(0x84);
  serial.write(0x43);
  serial.write(cmd);
  serial.write(msb);
  serial.write(lsb);
  serial.flush();
  delay(100);
}

@chaijunkun
Copy link

Hi bro, does this protocol support MPTZ-10?

@ca0abinary
Copy link
Author

Not sure, I've tested on MPTZ-6 and MPTZ-9, but I don't have an MPTZ-10. If you have the ability to test, please let me know if it works. This code is unlikely to damage a camera but be careful about how you hook up to it. The full repository is here if you'd like to contribute back: https://github.com/ca0abinary/Polycom-MPTZ-Protocol

@chaijunkun
Copy link

chaijunkun commented Oct 24, 2024

OK,I bought a breakout adapter for Polycom Eagle Eye IV which can convert HDCI camera interface to HDMI and RS232。Our campany disused a lot of that model's cameras, I plan to transform them into live streaming cameras. If it comes true, that will save much money. I can execute above commands via Raspbarry Pi 4B

@nukinel
Copy link

nukinel commented Nov 2, 2025

Im trying to initiate with 0x81,0x41 but only get constant F0. Have you ever seen F0 or any other code on the serial port?

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