If you've received your Marty after 2020 it's likely you have a version 2!
ESP Socket API
- 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
Celebrate makes Marty do a dance
tap_foot
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
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
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)
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: