Skip to content

SBUSEncoder

Python class that encodes the sbus protocol serial signal. The sbus protocol is an UART protocol that specifies the way to send a receive multiple channel information by using a conventional UART channel.

Each channel carries information about a control value. It is mainly used by RC receivers to control the angular movement of an object along its different axis.

Among the control values in an sbus protocol message are the known throttle, aileron, rudder and elevation.

Each manufacturer can have a slightly different implementation of the sbus protocol.

The sbus implementation for the Gremsy H16 requires that the idle, stop and parity bits are inverted w.r.t to their voltage value in a conventional UART communication. That is why a hardware inverter (i.e. 74HCN04) is used on the signal.

The following is the standard convention for the channels in Gremsy H16 (although it can be changed):

  1. Channel 2 is assumed to be elevation (pitch)

  2. Channel 4 is assumed to be pan (yaw)

  3. Channel 5 is assumed to be mode (lock, follow, off)

Source code in a2gmeasurements.py
class SBUSEncoder:
    """
    Python class that encodes the sbus protocol serial signal. The sbus protocol is an UART protocol that specifies the way to send a receive multiple channel information by using a conventional UART channel.

    Each channel carries information about a control value. It is mainly used by RC receivers to control the angular movement of an object along its different axis.

    Among the control values in an sbus protocol message are the known throttle, aileron, rudder and elevation.

    Each manufacturer can have a slightly different implementation of the sbus protocol.

    The sbus implementation for the Gremsy H16 requires that the idle, stop and parity bits are inverted w.r.t to their voltage value in a conventional UART communication. That is why a hardware inverter (i.e. 74HCN04) is used on the signal.

    The following is the standard convention for the channels in Gremsy H16 (although it can be changed):

    1. Channel 2 is assumed to be elevation (pitch)

    2. Channel 4 is assumed to be pan (yaw)

    3. Channel 5 is assumed to be mode (lock, follow, off)    
    """
    def __init__(self):
        """
        Initializes the channels.

        Establishes a linear mapping between the RC control value interval (-100, 100) and the actual values seen at the osciloscoppe of a given channel (i.e rudder).

        Define some attributes of the class. The atributes related with drifting are defined in the section Gremsy H16 Gimbal of the "Manual A2GMeasurements".
        """

        #self.channels = [1024] * 16
        self.channels = np.ones(16, dtype=np.uint16)*1024

        # This is from the oscilloscope
        #m, b = np.linalg.solve([[-99, 1], [100, 1]], [1870, 239])
        m, b = np.linalg.solve([[-99, 1], [0, 1]], [1870, 1055])

        # What intuitively should be is
        # m, b = np.linalg.solve([[-100, 1], [100, 1]], [0, 2047]) 

        # or according to some repositories, for FrSky receivers:
        #m, b = np.linalg.solve([[-100, 1], [100, 1]], [127, 1811])
        #m, b = np.linalg.solve([[-100, 1], [100, 1]], [237, 1864])
        #m, b = np.linalg.solve([[-100, 1], [100, 1]], [0, 2047])

        # Lowest speed experimentally found to counter drifting towards the right azimuth axis. 
        self.LOW_SPEED_COUNTER_rud = 8.74601226993865933

        # Drift towards the LEFT in azimuth, due to the use of LOW_SPEED_COUNTER_rud as the base speed (instead of 0)
        self.left_drifting_due_to_anti_drifiting = 10/75 # cm/s

        self.m = m
        self.b = b
        self.time_last_move_cmd = 0
        self.cnt = 1
        self.ENABLE_UPDATE_REST = True
        self.MODE = 'LOCK'

    def set_channel(self, channel, data):
        """
        Sets a value on a channel. This a "setter function".

        Args:
            channel (int): channel to be set.
            data (int): value to be set at the channel.
        """

        self.channels[channel] = data & 0x07ff    

    def encode_data(self):
        """
        Encodes the values on the channels according to the sbus protocol.

        Returns:
            packet (list of int): a 24-fields packet/msg that encodes the channel values according to sbus protocol.
        """

        #packet = np.zeros(25, dtype=np.uint8)
        packet = [0]*25
        packet[0] = 0x0F
        packet[1] = self.channels[0] & 0x7F
        packet[2] = ((self.channels[0] & 0x07FF)>>8 | (self.channels[1] & 0x07FF)<<3) & 0xFF
        packet[3] = ((self.channels[1] & 0x07FF)>>5 | (self.channels[2] & 0x07FF)<<6) & 0xFF
        packet[4] = ((self.channels[2] & 0x07FF)>>2) & 0xff
        packet[5] = ((self.channels[2] & 0x07FF)>>10 | (self.channels[3] & 0x07FF)<<1) & 0xff
        packet[6] = ((self.channels[3] & 0x07FF)>>7 | (self.channels[4] & 0x07FF)<<4) & 0xff
        packet[7] = ((self.channels[4] & 0x07FF)>>4 | (self.channels[5] & 0x07FF)<<7) & 0xff
        packet[8] = ((self.channels[5] & 0x07FF)>>1) & 0xff
        packet[9] =  ((self.channels[5] & 0x07FF)>>9 | (self.channels[6] & 0x07FF)<<2) & 0xff
        packet[10] = ((self.channels[6] & 0x07FF)>>6 | (self.channels[7] & 0x07FF)<<5) & 0xff
        packet[11] = ((self.channels[7] & 0x07FF)>>3) & 0xff
        packet[12] = (self.channels[8] & 0x07FF) & 0xff
        packet[13] = ((self.channels[8] & 0x07FF)>>8 | (self.channels[9] & 0x07FF)<<3) & 0xff
        packet[14] = ((self.channels[9] & 0x07FF)>>5 | (self.channels[10] & 0x07FF)<<6) & 0xff
        packet[15] = ((self.channels[10] & 0x07FF)>>2) & 0xff
        packet[16] = ((self.channels[10] & 0x07FF)>>10 | (self.channels[11] & 0x07FF)<<1) & 0xff
        packet[17] = ((self.channels[11] & 0x07FF)>>7 | (self.channels[12] & 0x07FF)<<4) & 0xff
        packet[18] = ((self.channels[12] & 0x07FF)>>4 | (self.channels[13] & 0x07FF)<<7) & 0xff
        packet[19] = ((self.channels[13] & 0x07FF)>>1) & 0xff
        packet[20] = ((self.channels[13] & 0x07FF)>>9 | (self.channels[14] & 0x07FF)<<2) & 0xff
        packet[21] = ((self.channels[14] & 0x07FF)>>6 | (self.channels[15] & 0x07FF)<<5) & 0xff
        packet[22] = ((self.channels[15] & 0x07FF)>>3) & 0xff
        packet[23] = 0x00
        packet[24] = 0x00

        # This is done to cope for the hardware inversion done on sbus signal before connecting it to the gimbal.
        for i in range(1, 23):
            packet[i] = ~packet[i] & 0xff

        return packet

    def start_sbus(self, serial_interface='/dev/ttyUSB', period_packet=0.009): #period_packet=0.009
        """
        Creates the serial connection between the host computer and the gimbal.

        Starts the repeating thread (RepeatTimer class instance) to send data each ``period_packet`` seconds. This mimics the behaviour between the RC transmitter and receiver.

        Args:
            serial_interface (str, optional): serial port. Defaults to ``/dev/ttyUSB``.
            period_packet (float, optional): time between calls of ``self.send_sbus_msg``. It was found that the communication is decoded when this value is lower than 20 ms. The actual value observed in the oscilloscope of the interval between sbus signal is 10-13ms (More in "Manual A2GMeasurements", section Gremsy H16 Gimbal). Defaults to 0.009.
        """

        #self.encoder = SBUSEncoder()
        self.serial_port = serial.Serial(serial_interface, baudrate=100000,
                                  parity=serial.PARITY_EVEN,
                                  stopbits=serial.STOPBITS_TWO)

        print('\n[DEBUG_0]: serial port connected')

        # Timer thread to leep sending data to the channels. This mimics the RC for the FrsKy X8R
        self.timer_fcn = RepeatTimer(period_packet, self.send_sbus_msg)  
        self.timer_fcn.start() 

        print('\n[DEBUG]: SBUS threading started')

    def stop_updating(self):
        """
        Stops the thread to repeatedly call ``self.send_sbus_msg``. Closes the opened serial port for this communication.
        """
        self.timer_fcn.cancel()
        self.serial_port.close()

    def send_sbus_msg(self):
        """
        Calls the channels encoder to write the message on the serial port.

        Since there is a known drifting in the yaw axis, this method sets a different 0 value (no movement) to counter the drifting behaviour. This is explained in "Manual A2GMeasurements" (Gremsy H16 Gimbal section), but a kind of equivalent way to understand this, is that we try to counter drift by changing the effective duty cycle of the yaw channel value.
        """
        if self.ENABLE_UPDATE_REST:
            self.update_rest_state_channel()
        data = self.encode_data()
        #self.serial_port.write(data.tobytes())
        self.serial_port.write(bytes(data))

    def update_channel(self, channel, value):
        """
        Update a channel given by "channel" with the value provided in "value".

        Args:
            channel (int): number of the channel: 1-16
            value (int): a number between  -100 and 100 representing the value of the channel
        """

        self.channels[channel-1] = int(self.m*value + self.b)
        #self.set_channel(channel, int(scale * 2047))

    def update_rest_state_channel(self):
        """
        Sets the no movement value (0) of the yaw channel (i.e. channel 4) to the experimentally found value that counters the drift. 

        Change ``parameter`` to change the "effective duty cycle" of the yaw channel value. More explanation is found in "Manual A2GMeasurements" (Gremsy H16 Gimbal section)
        """
        parameter = 2
        if self.cnt % parameter == 0:
            self.update_channel(channel=4, value=0)
        else:
            self.update_channel(channel=4, value=self.LOW_SPEED_COUNTER_rud)

        self.cnt = self.cnt + 1

    def not_move_command(self):
        """
        Updates the channel so that it does not continue moving.
        """

        self.update_channel(channel=1, value=0)
        self.update_channel(channel=2, value=0)
        self.update_channel(channel=3, value=0)
        #self.update_channel(channel=4, value=0)
        self.update_channel(channel=4, value=self.LOW_SPEED_COUNTER_rud)

        if self.MODE == 'LOCK':
            self.update_channel(channel=5, value=0)
        elif self.MODE == 'FOLLOW':
            self.update_channel(channel=5, value=-100)
        #time.sleep(0.1)

    def move_gimbal(self, ele, rud, mov_time):
        """
        Moves the gimbal a certain angle. The angle to be moved is determined by the RC control value for yaw, the RC control value for pitch, and the time those values are hold before realeasing them.

        The RC control values for yaw (``rud``) and pitch (``ele``) are values in the range (-100, 100) that behave as speed values: speed the gimbal will move in that particular axis (i.e. speed it will move in the yaw axis).

        Angle = Angular Speed x time

        Angular Speed = function of the RC control value

        Args:
            ele (int): RC control value for pitch. Can be thought as the pitch axis velocity. Between -100, 100. 
            rud (int): RC control value for yaw. Can be thought as the yaw axis velocity. Between -100, 100. 
            mov_time (int): time (seconds) to hold the velocity in a particular axis. Must be a positive value.
        """

        self.ENABLE_UPDATE_REST = False
        self.update_channel(channel=1, value=0)
        self.update_channel(channel=2, value=ele)
        self.update_channel(channel=3, value=0)
        self.update_channel(channel=4, value=rud)

        if self.MODE == 'LOCK':
            self.update_channel(channel=5, value=0)
        elif self.MODE == 'FOLLOW':
            self.update_channel(channel=5, value=-100)

        time.sleep(mov_time)
        self.not_move_command()
        self.ENABLE_UPDATE_REST = True
        self.time_last_move_cmd = datetime.datetime.now().timestamp()

    def turn_off_motors(self):
        """
        Turns off all gimbal motors.
        """
        self.update_channel(channel=1, value=0)
        self.update_channel(channel=2, value=0)
        self.update_channel(channel=3, value=0)
        self.update_channel(channel=4, value=0)
        self.update_channel(channel=5, value=100)

    def turn_on_motors(self):
        """
        Turns on all gimbal motors.
        """

        # Turn on motors and set the gimbal to lock mode
        self.update_channel(channel=5, value=0)

        # Turn on motors and set the gimbal to follow mode
        #self.update_channel(channel=5, value=-100)

    def change_mode(self, mode='LOCK'):
        """
        Changes the mode of all gimbal motors. According to manufacturers H16 manual the choices are: ``FOLLOW`` and ``LOCK``.

        Args:
            mode (str, optional): either "FOLLOW" or "LOCK". A description of each mode is on the manufacturer H16 manual. Defaults to ``LOCK``.
        """

        if mode == 'FOLLOW':
            self.update_channel(channel=5, value=-100)
        elif mode == 'LOCK':
            self.update_channel(channel=5, value=0)

__init__()

Initializes the channels.

Establishes a linear mapping between the RC control value interval (-100, 100) and the actual values seen at the osciloscoppe of a given channel (i.e rudder).

Define some attributes of the class. The atributes related with drifting are defined in the section Gremsy H16 Gimbal of the "Manual A2GMeasurements".

Source code in a2gmeasurements.py
def __init__(self):
    """
    Initializes the channels.

    Establishes a linear mapping between the RC control value interval (-100, 100) and the actual values seen at the osciloscoppe of a given channel (i.e rudder).

    Define some attributes of the class. The atributes related with drifting are defined in the section Gremsy H16 Gimbal of the "Manual A2GMeasurements".
    """

    #self.channels = [1024] * 16
    self.channels = np.ones(16, dtype=np.uint16)*1024

    # This is from the oscilloscope
    #m, b = np.linalg.solve([[-99, 1], [100, 1]], [1870, 239])
    m, b = np.linalg.solve([[-99, 1], [0, 1]], [1870, 1055])

    # What intuitively should be is
    # m, b = np.linalg.solve([[-100, 1], [100, 1]], [0, 2047]) 

    # or according to some repositories, for FrSky receivers:
    #m, b = np.linalg.solve([[-100, 1], [100, 1]], [127, 1811])
    #m, b = np.linalg.solve([[-100, 1], [100, 1]], [237, 1864])
    #m, b = np.linalg.solve([[-100, 1], [100, 1]], [0, 2047])

    # Lowest speed experimentally found to counter drifting towards the right azimuth axis. 
    self.LOW_SPEED_COUNTER_rud = 8.74601226993865933

    # Drift towards the LEFT in azimuth, due to the use of LOW_SPEED_COUNTER_rud as the base speed (instead of 0)
    self.left_drifting_due_to_anti_drifiting = 10/75 # cm/s

    self.m = m
    self.b = b
    self.time_last_move_cmd = 0
    self.cnt = 1
    self.ENABLE_UPDATE_REST = True
    self.MODE = 'LOCK'

change_mode(mode='LOCK')

Changes the mode of all gimbal motors. According to manufacturers H16 manual the choices are: FOLLOW and LOCK.

Parameters:

Name Type Description Default
mode str

either "FOLLOW" or "LOCK". A description of each mode is on the manufacturer H16 manual. Defaults to LOCK.

'LOCK'
Source code in a2gmeasurements.py
def change_mode(self, mode='LOCK'):
    """
    Changes the mode of all gimbal motors. According to manufacturers H16 manual the choices are: ``FOLLOW`` and ``LOCK``.

    Args:
        mode (str, optional): either "FOLLOW" or "LOCK". A description of each mode is on the manufacturer H16 manual. Defaults to ``LOCK``.
    """

    if mode == 'FOLLOW':
        self.update_channel(channel=5, value=-100)
    elif mode == 'LOCK':
        self.update_channel(channel=5, value=0)

encode_data()

Encodes the values on the channels according to the sbus protocol.

Returns:

Name Type Description
packet list of int

a 24-fields packet/msg that encodes the channel values according to sbus protocol.

Source code in a2gmeasurements.py
def encode_data(self):
    """
    Encodes the values on the channels according to the sbus protocol.

    Returns:
        packet (list of int): a 24-fields packet/msg that encodes the channel values according to sbus protocol.
    """

    #packet = np.zeros(25, dtype=np.uint8)
    packet = [0]*25
    packet[0] = 0x0F
    packet[1] = self.channels[0] & 0x7F
    packet[2] = ((self.channels[0] & 0x07FF)>>8 | (self.channels[1] & 0x07FF)<<3) & 0xFF
    packet[3] = ((self.channels[1] & 0x07FF)>>5 | (self.channels[2] & 0x07FF)<<6) & 0xFF
    packet[4] = ((self.channels[2] & 0x07FF)>>2) & 0xff
    packet[5] = ((self.channels[2] & 0x07FF)>>10 | (self.channels[3] & 0x07FF)<<1) & 0xff
    packet[6] = ((self.channels[3] & 0x07FF)>>7 | (self.channels[4] & 0x07FF)<<4) & 0xff
    packet[7] = ((self.channels[4] & 0x07FF)>>4 | (self.channels[5] & 0x07FF)<<7) & 0xff
    packet[8] = ((self.channels[5] & 0x07FF)>>1) & 0xff
    packet[9] =  ((self.channels[5] & 0x07FF)>>9 | (self.channels[6] & 0x07FF)<<2) & 0xff
    packet[10] = ((self.channels[6] & 0x07FF)>>6 | (self.channels[7] & 0x07FF)<<5) & 0xff
    packet[11] = ((self.channels[7] & 0x07FF)>>3) & 0xff
    packet[12] = (self.channels[8] & 0x07FF) & 0xff
    packet[13] = ((self.channels[8] & 0x07FF)>>8 | (self.channels[9] & 0x07FF)<<3) & 0xff
    packet[14] = ((self.channels[9] & 0x07FF)>>5 | (self.channels[10] & 0x07FF)<<6) & 0xff
    packet[15] = ((self.channels[10] & 0x07FF)>>2) & 0xff
    packet[16] = ((self.channels[10] & 0x07FF)>>10 | (self.channels[11] & 0x07FF)<<1) & 0xff
    packet[17] = ((self.channels[11] & 0x07FF)>>7 | (self.channels[12] & 0x07FF)<<4) & 0xff
    packet[18] = ((self.channels[12] & 0x07FF)>>4 | (self.channels[13] & 0x07FF)<<7) & 0xff
    packet[19] = ((self.channels[13] & 0x07FF)>>1) & 0xff
    packet[20] = ((self.channels[13] & 0x07FF)>>9 | (self.channels[14] & 0x07FF)<<2) & 0xff
    packet[21] = ((self.channels[14] & 0x07FF)>>6 | (self.channels[15] & 0x07FF)<<5) & 0xff
    packet[22] = ((self.channels[15] & 0x07FF)>>3) & 0xff
    packet[23] = 0x00
    packet[24] = 0x00

    # This is done to cope for the hardware inversion done on sbus signal before connecting it to the gimbal.
    for i in range(1, 23):
        packet[i] = ~packet[i] & 0xff

    return packet

move_gimbal(ele, rud, mov_time)

Moves the gimbal a certain angle. The angle to be moved is determined by the RC control value for yaw, the RC control value for pitch, and the time those values are hold before realeasing them.

The RC control values for yaw (rud) and pitch (ele) are values in the range (-100, 100) that behave as speed values: speed the gimbal will move in that particular axis (i.e. speed it will move in the yaw axis).

Angle = Angular Speed x time

Angular Speed = function of the RC control value

Parameters:

Name Type Description Default
ele int

RC control value for pitch. Can be thought as the pitch axis velocity. Between -100, 100.

required
rud int

RC control value for yaw. Can be thought as the yaw axis velocity. Between -100, 100.

required
mov_time int

time (seconds) to hold the velocity in a particular axis. Must be a positive value.

required
Source code in a2gmeasurements.py
def move_gimbal(self, ele, rud, mov_time):
    """
    Moves the gimbal a certain angle. The angle to be moved is determined by the RC control value for yaw, the RC control value for pitch, and the time those values are hold before realeasing them.

    The RC control values for yaw (``rud``) and pitch (``ele``) are values in the range (-100, 100) that behave as speed values: speed the gimbal will move in that particular axis (i.e. speed it will move in the yaw axis).

    Angle = Angular Speed x time

    Angular Speed = function of the RC control value

    Args:
        ele (int): RC control value for pitch. Can be thought as the pitch axis velocity. Between -100, 100. 
        rud (int): RC control value for yaw. Can be thought as the yaw axis velocity. Between -100, 100. 
        mov_time (int): time (seconds) to hold the velocity in a particular axis. Must be a positive value.
    """

    self.ENABLE_UPDATE_REST = False
    self.update_channel(channel=1, value=0)
    self.update_channel(channel=2, value=ele)
    self.update_channel(channel=3, value=0)
    self.update_channel(channel=4, value=rud)

    if self.MODE == 'LOCK':
        self.update_channel(channel=5, value=0)
    elif self.MODE == 'FOLLOW':
        self.update_channel(channel=5, value=-100)

    time.sleep(mov_time)
    self.not_move_command()
    self.ENABLE_UPDATE_REST = True
    self.time_last_move_cmd = datetime.datetime.now().timestamp()

not_move_command()

Updates the channel so that it does not continue moving.

Source code in a2gmeasurements.py
def not_move_command(self):
    """
    Updates the channel so that it does not continue moving.
    """

    self.update_channel(channel=1, value=0)
    self.update_channel(channel=2, value=0)
    self.update_channel(channel=3, value=0)
    #self.update_channel(channel=4, value=0)
    self.update_channel(channel=4, value=self.LOW_SPEED_COUNTER_rud)

    if self.MODE == 'LOCK':
        self.update_channel(channel=5, value=0)
    elif self.MODE == 'FOLLOW':
        self.update_channel(channel=5, value=-100)

send_sbus_msg()

Calls the channels encoder to write the message on the serial port.

Since there is a known drifting in the yaw axis, this method sets a different 0 value (no movement) to counter the drifting behaviour. This is explained in "Manual A2GMeasurements" (Gremsy H16 Gimbal section), but a kind of equivalent way to understand this, is that we try to counter drift by changing the effective duty cycle of the yaw channel value.

Source code in a2gmeasurements.py
def send_sbus_msg(self):
    """
    Calls the channels encoder to write the message on the serial port.

    Since there is a known drifting in the yaw axis, this method sets a different 0 value (no movement) to counter the drifting behaviour. This is explained in "Manual A2GMeasurements" (Gremsy H16 Gimbal section), but a kind of equivalent way to understand this, is that we try to counter drift by changing the effective duty cycle of the yaw channel value.
    """
    if self.ENABLE_UPDATE_REST:
        self.update_rest_state_channel()
    data = self.encode_data()
    #self.serial_port.write(data.tobytes())
    self.serial_port.write(bytes(data))

set_channel(channel, data)

Sets a value on a channel. This a "setter function".

Parameters:

Name Type Description Default
channel int

channel to be set.

required
data int

value to be set at the channel.

required
Source code in a2gmeasurements.py
def set_channel(self, channel, data):
    """
    Sets a value on a channel. This a "setter function".

    Args:
        channel (int): channel to be set.
        data (int): value to be set at the channel.
    """

    self.channels[channel] = data & 0x07ff    

start_sbus(serial_interface='/dev/ttyUSB', period_packet=0.009)

Creates the serial connection between the host computer and the gimbal.

Starts the repeating thread (RepeatTimer class instance) to send data each period_packet seconds. This mimics the behaviour between the RC transmitter and receiver.

Parameters:

Name Type Description Default
serial_interface str

serial port. Defaults to /dev/ttyUSB.

'/dev/ttyUSB'
period_packet float

time between calls of self.send_sbus_msg. It was found that the communication is decoded when this value is lower than 20 ms. The actual value observed in the oscilloscope of the interval between sbus signal is 10-13ms (More in "Manual A2GMeasurements", section Gremsy H16 Gimbal). Defaults to 0.009.

0.009
Source code in a2gmeasurements.py
def start_sbus(self, serial_interface='/dev/ttyUSB', period_packet=0.009): #period_packet=0.009
    """
    Creates the serial connection between the host computer and the gimbal.

    Starts the repeating thread (RepeatTimer class instance) to send data each ``period_packet`` seconds. This mimics the behaviour between the RC transmitter and receiver.

    Args:
        serial_interface (str, optional): serial port. Defaults to ``/dev/ttyUSB``.
        period_packet (float, optional): time between calls of ``self.send_sbus_msg``. It was found that the communication is decoded when this value is lower than 20 ms. The actual value observed in the oscilloscope of the interval between sbus signal is 10-13ms (More in "Manual A2GMeasurements", section Gremsy H16 Gimbal). Defaults to 0.009.
    """

    #self.encoder = SBUSEncoder()
    self.serial_port = serial.Serial(serial_interface, baudrate=100000,
                              parity=serial.PARITY_EVEN,
                              stopbits=serial.STOPBITS_TWO)

    print('\n[DEBUG_0]: serial port connected')

    # Timer thread to leep sending data to the channels. This mimics the RC for the FrsKy X8R
    self.timer_fcn = RepeatTimer(period_packet, self.send_sbus_msg)  
    self.timer_fcn.start() 

    print('\n[DEBUG]: SBUS threading started')

stop_updating()

Stops the thread to repeatedly call self.send_sbus_msg. Closes the opened serial port for this communication.

Source code in a2gmeasurements.py
def stop_updating(self):
    """
    Stops the thread to repeatedly call ``self.send_sbus_msg``. Closes the opened serial port for this communication.
    """
    self.timer_fcn.cancel()
    self.serial_port.close()

turn_off_motors()

Turns off all gimbal motors.

Source code in a2gmeasurements.py
def turn_off_motors(self):
    """
    Turns off all gimbal motors.
    """
    self.update_channel(channel=1, value=0)
    self.update_channel(channel=2, value=0)
    self.update_channel(channel=3, value=0)
    self.update_channel(channel=4, value=0)
    self.update_channel(channel=5, value=100)

turn_on_motors()

Turns on all gimbal motors.

Source code in a2gmeasurements.py
def turn_on_motors(self):
    """
    Turns on all gimbal motors.
    """

    # Turn on motors and set the gimbal to lock mode
    self.update_channel(channel=5, value=0)

update_channel(channel, value)

Update a channel given by "channel" with the value provided in "value".

Parameters:

Name Type Description Default
channel int

number of the channel: 1-16

required
value int

a number between -100 and 100 representing the value of the channel

required
Source code in a2gmeasurements.py
def update_channel(self, channel, value):
    """
    Update a channel given by "channel" with the value provided in "value".

    Args:
        channel (int): number of the channel: 1-16
        value (int): a number between  -100 and 100 representing the value of the channel
    """

    self.channels[channel-1] = int(self.m*value + self.b)

update_rest_state_channel()

Sets the no movement value (0) of the yaw channel (i.e. channel 4) to the experimentally found value that counters the drift.

Change parameter to change the "effective duty cycle" of the yaw channel value. More explanation is found in "Manual A2GMeasurements" (Gremsy H16 Gimbal section)

Source code in a2gmeasurements.py
def update_rest_state_channel(self):
    """
    Sets the no movement value (0) of the yaw channel (i.e. channel 4) to the experimentally found value that counters the drift. 

    Change ``parameter`` to change the "effective duty cycle" of the yaw channel value. More explanation is found in "Manual A2GMeasurements" (Gremsy H16 Gimbal section)
    """
    parameter = 2
    if self.cnt % parameter == 0:
        self.update_channel(channel=4, value=0)
    else:
        self.update_channel(channel=4, value=self.LOW_SPEED_COUNTER_rud)

    self.cnt = self.cnt + 1