Esp Socket Api

These articles are for Marty the Robot version 1

If you've received your Marty after 2020 it's likely you have a version 2!

ESP Socket API


This page refers to the following specific versions or dependencies:
Rick Main Firmware
v.1.0.0
Rick ESP Firmware
v.1.0.0
Rick Control Poard PCB
v.1.0.0


The ESP8266 WiFi modem on Rick presents a low-level TCP Socket and Web Socket API to the network. Clients connecting to this API can send byte-level commands to the ESP which will in turn instruct the main control chip to perform an action or report back on a sensor. The socket API runs over port 24. It’s also accessible over Serial or i2c, in ROSSerial format with topicID corresponding to socket_cmd (112)

There’s a Test Harness available that can connect to Marty and just print out a stream of Sensor data. This uses JavaScript and a Web Socket connection. Incidentally, this is how Scratch talks to Marty.


Ports & Services

Number Use
23 Unmanaged sockets
24 Managed sockets
80 “Marty Setup” functionality, JavaScript-based service discovery
81 Websockets
4000 UDP-based service discovery

Managed sockets follow the below socket API, while Unmanaged sockets are given raw pass-through access to the Serial line between the ESP and the main STM446 microcontroller.

In almost all use-cases, the managed socket is what you’ll want to use.



Socket API

Over Ports 23, 24 & 81

GET Type Packets

These GET requests fetch sensory information. A GET request is characterised by the zeroth packet byte being 0x01. The 2nd byte then selects the sensor type, and the third selects the ID, if applicable. Where the Type is undefined, any value may be sent (e.g. 0x00) but it must be there so the packet length is correct. All GET packets are of length 3.

Byte 0 Byte 1 Byte 2
0x01 Type ID

Currently there are 4 types of sensor queryable: Battery voltage, Accelerometer, Motor Currents and GPIOs.

Sensor Type Byte ID Byte ID Description Return type
Battery 0x01 (undefined) Not applicable float32
Accelerometer 0x02 [0x00, …, 0x02] x, y and z axes float32
Motor Current 0x03 [0x00, …, 0x07] Motors 0 through 7 float32
GPIO 0x04 [0x00, …, 0x07] GPIOs 0 through 7 float32
Chatter 0x05 (undefined) Not applicable int32 length, string
Motor Position 0x06 [0x00, …, 0x08] Motors 0 through 8 int8
Motor Enabled 0x07 [0x00, …, 0x08] Motors 0 through 8 bool

Battery, Accelerometer, Motor current, and GPIO readings returned after a GET request are a 4 Byte Little-Endian float, i.e. the LSB is first, the MSB last.

Chatter returns a packet length as a 4-byte Little-Endian int, then a null terminated string.

Motor Position returns an int8, in the range -100 to +100.

Motor enabled returns a bool.

The 8th motor channel (usually the eyes on Marty) does not have a current sensor so no value can be reported.


COMMAND Type Packets

Command packets intstruct the control board to do something. Similar to the GET type packets, COMMAND packets all begin 0x02. This type of packet can vary in length depending on the operation, so the 1st and 2nd bytes encode the payload size (number of bytes), though don’t count themselves nor the zeroth byte in the size.

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte M
0x02 Size LSB Size MSB Command Opcode 1st Arg Nth Arg

The payload size (1st and 2nd bytes) should be encoded as a little endian integer. Bytes in position 4 through M depend on the opcode and command being called.

Here is a summary table of the commands, more detail on specific functions is below.

Command Size Opcode Arguments
hello 1 (2) 0x00 [uint8 type (default 0)]
lean 5 0x02 uint8 direction, int8 amount, uint16 move_time
walk 7 0x03 uint8 steps, uint8 turn, uint16 move_time,
int8 step_length, int8 side
kick 5 0x05 uint8 side, int8 twist, uint16 move_time
celebrate 3 0x08 uint16 move_time
tap_foot 2 0x0A int8 side
arms 5 0x0B int8 r_angle, int8 l_angle, uint16 move_time
sidestep 6 0x0E int8 side, int8 num_steps, uint16 move_time, int8 step_length
stand straight 3 0x0F uint16 move_time
play_sound 7 0x10 uint16 freq_start, uint16 freq_end, uint16 duration
stop 2 0x11 uint8 stop_type (0x00, …, 0x05)
move_joint 5 0x12 uint8 joint_id, int8 position, uint16 move_time
enable_motors 4 0x13 [uint16_t motorFlags, [int8_t mode (0x00, 0x01)]]
disable_motors 4 0x14 [uint16_t motorFlags, [int8_t mode (0x00, 0x01)]]
fall_protection 2 0x15 bool enabled (0x00 or 0x01)
motor_protection 2 0x16 bool enabled (0x00 or 0x01)
low_battery_cutoff 2 0x17 bool enabled (0x00 or 0x01)
buzz_prevention 2 0x18 bool enabled (0x00 or 0x01)
set_IO_type 3 0x19 uint8 io_number, int8 type
IO_write 6 0x1A uint8_t io_number, float value
i2c_write 1+n 0x1B uint8[n] address + data
circle_dance 4 0x1C uint8 side, uint16 move_time
lifelike_behaviours 2 0x1D bool enabled (0x00 or 0x01)
enable_safeties 1 0x1E -none-
set_parameter 2+n 0x1F uint8 paramID, params
get_firmware_version 1 0x20 -none-
mute_esp_serial 1 0x21 -none-
clear_calibration 1 0xFE -none-
save_calibration 1 0xFF -none-


direction / side codes

Code
0x00 Left
0x01 Right
0x02 Forward
0x03 Backward
0x04 Any

move_time

Move time specifies the number of milliseconds for a movement to take, and is always a uint16 with LSB first. So, to take 1 second, or 1,000 ms, send bytes [0xE8, 0x03].

hello

uint8 type (default 0)

Type
0 Default, move joints to zero position and wiggle eyebrows
1 Try to force enable. If Marty is out of position, this will try to
ensure that Marty ends up centred with motors enabled.

lean

uint8 direction, int8 amount, uint16 move_time

amount is an int specifying percentage of max, so 0-100.

walk

uint8 steps, int8 turn, uint16 move_time, int8 step_length, int8 side

steps is a uint8 specifying the number of steps
turn specifies the amount to turn per step as a percentage, so -100 to 100
step_length specifies the step length as a percentage of max, so -100 to 100. Negative step lengths will walk backwards.
move_time and side are as defined above

kick

uint8 side, int8 twist, uint16 move_time

twist is the amount to twist the knee joint while kicking, from -100 to +100. It can be used to kick at an angle.

celebrate

uint16 move_time

Celebrate makes Marty do a dance

tap_foot

int8 side

Tap_foot will make Marty tap one of his feet three times

arms

int8 r_angle, int8 l_angle, uint16 move_time

r_angle is the right arm position, from -100 to +100 l_angle is the left arm position, from -100 to +100

sidestep

int8 side, uint8 num_steps, uint16 move_time, int8 step_length

num_steps is a uint8 specifying the number of steps step_length is an int8 specifying the step length, as a percentage of max, so 0 to 100

stand_straight

uint16 move_time

Stand_straight returns all motors to the zero positions

play_sound

uint16 freq_start, uint16 freq_end, uint16 duration

play_sound will make the activate the buzzer on Marty, it’ll start at freq_start and finish at freq_end after duration milliseconds. These sounds will queue on Marty, so multiple play_sound commands can be used to queue a tune.

freq_start is a uint16, LSB first, specifying the starting frequency in Hz freq_end is a uint16, LSB first, specifying the ending frequency in Hz

stop

uint8 stop_type

Code Stop type
0 Clear movement queue only (so finish the current step/wiggle/movement)
1 Clear movement queue and servo queues (freeze where you are)
2 Clear everything and disable motors
3 Clear everything, and make robot return to zero
4 Pause, but keep servo and movequeue intact and motors enabled
5 As 4, but disable motors too

move_joint

uint8 joint_id, int8 position, uint16 move_time

joint_id the specific joint to move position an int8 from -127 to +127 as a percentage of maximum servo movement. Note that not all motors can physically move through the whole range in Marty

Joint ID Joint in Marty
0 Left hip
1 Left twist
2 Left knee
3 Right hip
4 Right twist
5 Right knee
6 Left arm
7 Right arm
8 Eyes

enable_motors

[uint16_t motor_flags, [int8_t mode (0x00, 0x01)]]

enable_motors can be called with optional paramters. It will allow motors to be given positions, but won’t by itself move the motors anywhere.
# It will also unpause movement.

motor_flags is a uint16 specifying which motors should be enabled. Byte 0 (the LSB) corresponds to motor 0, so to enable motors 0 and 5, you would send b0000 0000 0010 0001 (0x0021). motor_flags defaults to 0xFFFF (all motors) if not specified mode can be 0 to enable immediately, or 1 to enable at the end of the current movement queue

disable_motors

[uint16_t motor_flags, [int8_t mode (0x00, 0x01)]]

disable_motors can be called with optional paramters. It will make the specified motors become idle, i.e. unpowered and able to be freely moved.

motor_flags is a uint16 specifying which motors should be disabled. Byte 0 (the LSB) corresponds to motor 0, so to disable motors 0 and 5, you would send b0000 0000 0010 0001 (0x0021). motor_flags defaults to 0xFFFF (all motors) if not specified mode can be 0 to disable immediately, or 1 to disable at the end of the current movement queue

fall_protection

bool enabled

Fall protection will disable all motors if it detects Marty is falling. It does this by measuring the Z axis of the accelerometer, and reacting when it passes a threshold. The accelerometer signal is digitally low pass filtered to try and prevent false positives. When fall protection is enabled, a change in fall state will be published on the chatter topic, and the servos_enabled will show deactivation of the servos.

enabled should be 1 to activate fall protection, or 0 to deactive it. Fall protection is disabled on startup.

It is stongly recommended that fall protection is activated when Marty is in use, as this can save your motors, especially if your robot falls from a height.

motor_protection

bool enabled

Motor protection uses the build in current sensors on motors 0-7 to deactivate a specific motor if it senses it has become overloaded. This is on by default, and deactivating it should not be done unless you’re really sure you want to. Deactivating motor protection will void the warranty on your motors.

enabled should be 1 to activate, or 0 to deactivate. Enabled on startup.

low_battery_cutoff

bool enabled

This will start beeping if the voltage on the battery starts to get too low (below 7.0v or 7.4v depending on settings). After a minute of prolonged undervoltage, it will disable the motors. This is enabled on startup.

Marty’s control board has built in undervoltage cutoffs to protect LiPo batteries, and the supplied battery also has its own low voltage cutoff. However, due to large current draws when motors are driven, the battery voltage can fluctuate, and it is not advisable to let the robot reach these hard cutoffs. Therefore, the software low_battery_cutoff is recommended to be used.

enabled should be 1 to activate, or 0 to deactivate. Enabled on startup.

buzz_prevention

bool enabled

This feature will attempt to stop servo ‘buzz’, which occurs when a servo is slightly away from its commanded position, but the motor signal isn’t quite strong enough to move the output. It will adjust the commanded output position to try and match the real position. This feature is strongly recommended, as it’ll help prolong the life of your servos, and make the robot sound better as it’ll get itself comforable after completing a movement.

This feature also allows for manual movement of the joints, so you can push a joint to adjust its position.

note that buzz prevention is not on the eyes, so don’t try to move them manually

enabled should be 1 to activate, or 0 to deactivate. Disabled on startup.

set_IO_type

uint8 io_number, int8 type

Used to set the type of GPIO port.

io_number is the port number, from 0-7 type can be 0 for digital input (default), or 2 for digital output.

More types will be supported in the future, but v1.0.0 of Rick can only support digital in/out.

IO_write

uint8_t io_number, float32 value

Change the state of an output pin. Pin must have already been configured with set_IO_type.

io_number is the port number, from 0-7 value is a 4-byte float with the value to output.

It’s a bit of overkill to use a float when currently only digital out is supported, but we’ll be adding PWM out and potentially Analog out in the future, so we have float for futureproofing.

i2c_write

uint8[n] address + data

Sends the data bytes over i2c. The first data byte should be the address, usual i2c parlance etc. This will transmit over i2c1, the one connected to the accelerometer and broken out next to the GPIO pins. Address 0x3A is the on board accelerometer, so avoid adding another slave device with this address.

circle_dance

uint8 side, uint16 move_time

Moves the hips and knees to move head in a circle. side specifies whether to start with left or right, and whether to go clockwise or anticlockwise. Will leave Marty leaning forwards, and you can send multiple commands to queue multiple circles.

lifelike_behaviours

bool enabled

Lifelike behaviours will make Marty do something after a minute of inactivity, and every minute after that. Behaviours include tapping feet, swinging arms, looked angry, etc. After each behaviour Marty’s eyebrows will move to indicate battery voltage, he’ll get angrier as his battery runs down.

enabled should be 1 to activate, or 0 to deactivate

enable_safeties

-no parameters-

Enable safeties will activate fall protection, buzz prevention, and increase the battery cutoff voltage. It should be called normally at the start of operation, as it puts Marty into the recommended operating state.

set_parameter

uint8 paramID, params

Note that these parameters are not persistent. They are stored in RAM and will be reset to default when the board is power cycled.

Param ID Data Description
0 uint8 lean_amount Changes amount of side to side movement when walking etc.
leanAmount is a percentage of nominal, from 0-200. So setting leanAmount to 100 will revert to normal.
2 uint16_t topicID, uint16_t period Rostopic publishing period
Period is specified in milliseconds
topicID is the rosserial topic ID, so: Accel: 104; GPIO: 106; Battery: 107; Motor currents: 108; Servo positions: 113
3 uint8 jointID, float32 threshold Change the instantaneous current limit for a particular motor. Currently strong motors have a threshold of 0.022 by default, and weak motors of 0.017
4 uint8 jointID, float32 threshold Change the threshold on the leaky integrator for continuous overcurrent detection. Currently strong motors have a threshold of 12.0 by default, and weak motors of 9.0

get_firmware_version

-no parameters-

Will cause the firmware version to be published on the chatter topic

mute_ESP_serial

-no parameters-

Will make the ST micro’s serial lines for ESP comms go to high impedance. This is useful for two reasons:

  • To program the ESP
  • To stop the ESP being annoying when connected to a pi over the other serial port

Note that calling this will make the control board unable to receive commands over wifi until the next reset.

clear_calibration

-no parameters-

Clear servo calibrations, and calibrated flag from flash memory. Currently requires a power cycle before calibrations in RAM will be cleared

save_calibration

-no parameters-

Save current positions as zero position. Note that all servos must be enabled and in commanded positions for save calibration to work. Check the servo_positions topic to make sure none of the motors are at -100

Success or failure of calibration will be published over the chatter topic

ROS COMMAND Type Packets

The ROS COMMAND packet format is the lowest level of access offered by the TCP Socket API, and simply passes on the Data byte array to the main controller chip via ROS Serial. The size bytes are similar to the COMMAND packet format’s size bytes, giving the length of the Data array in bytes.

Byte 0 Byte 1 Byte 2 Byte 3 Byte M
0x03 Size LSB Size MSB Data[0] Data[N-1]

More information on ROS (and ROS Serial) is given here


Simple Sockets Example in Python 3

Just to illustrate how the socket API works, though if you’re using Python to communicate with Marty we’d recommend you check out the MartyPy api documented here through which you can access lower-level socket stuff if you want (in the Marty.client.sock member)

import socket
import struct

ip = '192.168.0.42' # Adjust accordingly
port = 24

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))

# Send the Accelerometer x Axis Command
sock.send(''.join([chr(b) for b in [0x01, 0x02, 0x00]]).encode('ascii'))

accel_raw = sock.recv(4)
accel = struct.unpack('f', accel_raw)[0]




Other Endpoints

Over Ports 80 & 4000

http://______:80/service-discovery

A Marty will respond with a HTTP 200 OK with AA in the body, followed by the Robot’s name (if configured). The ______ part should be replaced with the Marty’s IP address as appropriate.

This can be used for ‘brute force’ discovery of robots by sending the same request to all addresses in a range you expect a Marty to be on.

Note that this may not play well with firewalls or routing policies.

http://______:80/

The board will drop into a special ‘hotspot mode’ if it can’t connect to a Wireless network. If you then connect to this network (called ‘Marty Setup’ followed by some digits) you’ll be presented with the config page.

This enpoint will only be exposed when wither 1) the Marty cannot connect to a WiFi network or 2) you press Bob the Button, which will make a noise and bring the hotspot up.

UDP port 4000

Martys will respond to a Multicast UDP packet AA, giving their name and IP. Sockets can be fiddly and multicast can be particularly fiddly, so mileage will vary based on a combination of operating system, local network settings and hardware. The multicast address can also vary.

As a minimal example, this works for us in Python 3:

import socket

socket_addr = "224.0.0.1"
socket_port = 4000
magic_command = b"AA"

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32)
sock.sendto(magic_command, (socket_addr, socket_port))

while True:
    data, addr = sock.recvfrom(1000)
    print("{}: {}".format(addr, data))



Task Runner