izzy-heartbeat documentation

To address emergency stop requirements and loss of communication safety concerns, the IZZY project implements a heartbeat protocol between IZZY client devices and Mother servers. When the heartbeat signal is lost, IZZY clients shutdown. Hitting an e-stop button wherever the Mother application is running cuts off the heartbeat signal, triggering a shutdown and system reset.

Upon startup, Mother servers begin sending out Heartbeat hello message packets as broadcast UDP messages. Any powered up IZZY clients on the same network will respond to hello messages by sending a here message packet which contains a data payload with status information about the client. The status information can change depending on the operating mode the client is in; at a minimum, an IZZY client Heartbeat response will include its user-friendly name, its status/operating mode, its position in x, y, and z directions, its heading, and its current speed.

Heartbeat Messages and Packets

Heartbeat messages are programmatically created and stored in custom HeartbeatMessage objects. Heartbeat packets, the raw UDP data that is sent and received, comprise up to 255 bytes of information (46 required bytes and up to 209 status/data payload bytes). Packets begin with a one-byte preamble (0x10) and are followed immediately by an 11-byte IZZY project identifier (“izzymessage,” or 0x69 0x7A 0x7A 0x79 0x6D 0x65 0x73 0x73 0x61 0x67 0x65). The next byte contains the total length of the packet. The next 16 bytes contain the 64-bit UUID of the sending device followed by the 64-bit UUID of the receiving device. This is followed by a one-byte Message Type identifier, then the data payload, which can be up to 209 bytes long.


Heartbeat Message Packet Structure

Preamble

Message ID

Message Length

Sender UUID

Receiver UUID

Message Type

Payload

0x10

0x69 0x7A 0x7A 0x79 0x6D 0x65 0x73 0x73 0x61 0x67 0x65

1 byte

8 bytes

8 bytes

1 byte

0-209 bytes

HeartbeatMessages (instances of the HeartbeatMessage class) are different than raw Heartbeat packets in that each instance includes attributes for storing each chunk of a packet separately. This allows for easy programmatic manipulation of the message data when necessary.

Note

Improperly structured Heartbeat packets will not be interpreted correctly.

Threading

Heartbeat sending and receiving happens on separate threads than the rest of the application, whether on a client or a server. This allows for non-blocking processing and for (relatively) timely delivery and response of heartbeat packets. The package provides three classes to facilitate this: HeartbeatServerThread, HeartbeatListenerThread, and HeartbeatResponderThread.

HeartbeatMessage

class heartbeat.HeartbeatMessage(message_type=None)

Generates, holds, and deconstructs the bytes in a Heartbeat packet.

preamble

The first byte of a heartbeat packet. Must be 0x10.

msg_id

11-byte sequence identifying the packet as an IZZY heartbeat packet (‘izzymessage’ or 0x69, 0x7A, 0x7A, 0x79, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65).

msg_length

A byte representing the total number of bytes in the packet.

sender_id

The 64-bit UUID of the sending device.

receiver_id

The 64-bit UUID of the receiving device.

message_type

A byte representing the Heartbeat MessageType.

data

The data payload.

HeartbeatMessage constructor.

Parameters:

message_type – Takes values of enumerated pre-defined MessageTypes. Defaults to none. In most cases, Mother will generate messages of type HELLO (0x01).

get_message()
Returns:

a bytearray containing the sequence of bytes in the packet.

Return type:

bytearray

process_packet(raw_data)

Takes a packet of received data (as a bytearray) and unpacks it, filling the attributes of the instance of HeartbeatMessage with the appropriate bytes provided the length of the packet matches the length byte sent in the packet.

Parameters:

raw_data – a bytearray containing a raw packet

Returns:

True if the packet is the correct length and the bytes in the packet have populated the HeartbeatMessage instance; otherwise False.

Return type:

boolean

set_data(data)

Fills the data payload of the message, and updates the total length of the message by adding the length of the data payload to the base length of the message (46 bytes).

Parameters:

data – A bytearray of data.

HeartbeatServerThread

class heartbeat.HeartbeatServerThread(send_socket, message, interval=1)

Creates a separate worker thread for sending HeartbeatMessage packets.

send_socket (socket)

UDP socket for sending Heartbeat packets.

message (HeartbeatMessage)

The bytes of a HeartbeatMessage to send (as a bytearray).

interval (int)

The delay between packet pulses (in seconds).

_running (boolean)

A boolean to control stopping the thread, if necessary.

Default constructor. Extends threading.Thread.

Parameters:
  • (socket) (send_socket) – a socket for sending packets

  • (HeartBeatMessage) (message) – a message to send

  • (int) (interval) – time interval between sending packets, in seconds

run()

Starts the HeartbeatThread, which will run as long as the _running flag is True. Sends the message and after waiting the specified interval, in a loop.

stop()

Sets the _running flag to False.

HeartbeatListenerThread

class heartbeat.HeartbeatListenerThread(rcv_socket, messages, signal)

Creates a separate worker thread for receiving Heartbeat message packets. Packets are stored as raw bytes.

rcv_socket (socket)

UDP socket for receiving Heartbeat response packets.

_running (boolean)

A boolean to control stopping the thread, if necessary.

hb_messages (Queue)

A Queue for placing received messages.

message (HeartbeatMessage)

A HeartbeatMessage instance for packaging received packets as a message.

Default constructor. Extends threading.Thread.

Parameters:
  • (socket) (rcv_socket) – UDP socket for receiving Heartbeat response packets.

  • (Queue) (messages) – A Queue for placing received messages.

  • (Signal) (signal) – A signalslot Signal for triggering other actions when a message is received.

run()

Starts the HeartbeatListenerThread, which will run as long as the _running flag is True. Receives UDP packets, extracts the IP address and port of the sender, and creates a new HeartbeatMessage populated from the packet. Then it places a tuple containing the length of the packet, the address of the sender (as a tuple with the IP address and the port number), and the packet itself on the message queue.

stop()

Sets the _running flag to False.

HeartbeatResponderThread

class heartbeat.HeartbeatResponderThread(send_socket, hb_messages, izzy, mother)

This constructor should always be called with keyword arguments. Arguments are:

group should be None; reserved for future extension when a ThreadGroup class is implemented.

target is the callable object to be invoked by the run() method. Defaults to None, meaning nothing is called.

name is the thread name. By default, a unique name is constructed of the form “Thread-N” where N is a small decimal number.

args is a list or tuple of arguments for the target invocation. Defaults to ().

kwargs is a dictionary of keyword arguments for the target invocation. Defaults to {}.

If a subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread.

run()

Method representing the thread’s activity.

You may override this method in a subclass. The standard run() method invokes the callable object passed to the object’s constructor as the target argument, if any, with sequential and keyword arguments taken from the args and kwargs arguments, respectively.

Message Types

MessageType.py

Enumerates Heartbeat message types.

enum message_type.MessageType(value)

An enumeration class for heartbeat message types. Extends class Enum from enum.

Valid values are as follows:

HELLO = <MessageType.HELLO: 1>
HERE = <MessageType.HERE: 2>
SETUP_ERROR = <MessageType.SETUP_ERROR: 3>
MOVING = <MessageType.MOVING: 4>
FOLLOWING = <MessageType.FOLLOWING: 5>
ESTOP = <MessageType.ESTOP: 6>
OSC_COM_ERROR = <MessageType.OSC_COM_ERROR: 7>
NOT_VALID = <MessageType.NOT_VALID: 8>

Port Numbers

Ports.py

Enumerates UDP port numbers for Heartbeat and OSC communication.

enum ports.Ports(value)

An enumeration class for network ports used by the project. Extends class Enum from enum.

Valid values are as follows:

UDP_TO_CLIENT_PORT = <Ports.UDP_TO_CLIENT_PORT: 9001>
UDP_FROM_CLIENT_PORT = <Ports.UDP_FROM_CLIENT_PORT: 9000>
OSC_TO_CLIENT_PORT = <Ports.OSC_TO_CLIENT_PORT: 8001>
OSC_FROM_CLIENT_PORT = <Ports.OSC_FROM_CLIENT_PORT: 8000>