Skip to content

blgimbal

plantimager.blgimbal Link

Implementation of a class dedicated to controlling a gimbal.

This gimbal implementation is based on a custom-built controller. The gimbal is used to orient a camera by controlling pan & tilt orientations. The control is done by a serial port that has to be defined.

Note that this module offer the ability to use one or two motors. With one, you control the pan orientation, this is like turning your head left or right. With two, you can also control the tilt, this is like looking up or down. In conjunction with a 3-axis CNC, that gives a total of 5 degrees of freedom and thus the ability to scan the whole volume around the plant.

Gimbal Link

Gimbal(port='/dev/ttyUSB0', baudrate=115200, has_tilt=True, steps_per_turn=360, zero_pan=0, zero_tilt=0, invert_rotation=False)

Bases: AbstractGimbal

Custom-built gimbal controller based on Feather M0 and a .

Attributes:

Name Type Description
port str

Serial port to use for communication with the Gimbal controller (Feather M0).

baudrate (int, optional)

Communication baud rate, 115200 should work with the Feather M0.

status str

Describe the gimbal current status, "idle" or "moving".

p [float, float]

Current pan and tilt positions.

serial_port Serial

The Serial instance used to send commands to the gimbal.

zero_pan float

The angle, in degree, to use as zero for pan.

zero_tilt float

The angle, in degree, to use as zero for tilt.

has_tilt bool

Indicate if the gimbal has a tilt motor or just a pan motor.

steps_per_turn int

Number of steps required to complete a full rotation.

invert_rotation bool

Use it to invert the rotation of the motor.

See Also

plantimager.hal.AbstractGimbal

Examples:

>>> from plantimager.blgimbal import Gimbal
>>> g = Gimbal("Feather M0", has_tilt=False, invert_rotation=True)
>>> g.status

Constructor.

Parameters:

Name Type Description Default
port str

Serial port to use for communication with the Gimbal controller. This can also be a regular expression to identify in a unique way the corresponding port. Defaults to "/dev/ttyUSB0".

'/dev/ttyUSB0'
baudrate int

Communication baud rate, 115200 should work with the Feather M0.

115200
has_tilt bool

Indicate if the Gimbal has a tilt motor. Defaults to False.

True
steps_per_turn int

Indicate the number of steps to perform for a full revolution (360°). Defaults to 360.

360
zero_pan float

Indicate the origin position of the Gimbal for the pan-axis. Defaults to 0..

0
zero_tilt float

Indicate the origin position of the Gimbal for the tilt-axis. Defaults to 0..

0
invert_rotation bool

Indicate if rotation direction should be inverted. Defaults to False.

False
Source code in plantimager/blgimbal.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def __init__(self, port="/dev/ttyUSB0", baudrate=115200, has_tilt=True, steps_per_turn=360,
             zero_pan=0, zero_tilt=0, invert_rotation=False):
    """Constructor.

    Parameters
    ----------
    port : str, optional
        Serial port to use for communication with the Gimbal controller.
        This can also be a regular expression to identify in a unique way the corresponding port.
        Defaults to `"/dev/ttyUSB0"`.
    baudrate : int, optional
        Communication baud rate, `115200` should work with the Feather M0.
    has_tilt : bool, optional
        Indicate if the Gimbal has a tilt motor.
        Defaults to ``False``.
    steps_per_turn : int, optional
        Indicate the number of steps to perform for a full revolution (360°).
        Defaults to ``360``.
    zero_pan : float, optional
        Indicate the origin position of the Gimbal for the pan-axis.
        Defaults to ``0.``.
    zero_tilt : float, optional
        Indicate the origin position of the Gimbal for the tilt-axis.
        Defaults to ``0.``.
    invert_rotation : bool, optional
        Indicate if rotation direction should be inverted.
        Defaults to ``False``.

    """
    super().__init__()
    self.port = port if port.startswith('/dev') else guess_port(port)
    self.baudrate = baudrate
    self.status = "idle"
    self.p = [0, 0]
    self.serial_port = None
    self.zero_pan = zero_pan
    self.zero_tilt = zero_tilt
    self.has_tilt = has_tilt
    self.steps_per_turn = steps_per_turn
    self.invert_rotation = invert_rotation
    self.start()
    atexit.register(self.stop)

__send Link

__send(s)

Send a command trough the serial port.

Parameters:

Name Type Description Default
s str

The command to send to the custom board controlling the Gimbal.

required
Source code in plantimager/blgimbal.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def __send(self, s):
    """Send a command trough the serial port.

    Parameters
    ----------
    s : str
        The command to send to the custom board controlling the Gimbal.
    """
    if not self.serial_port:
        raise Error("Serial connection to gimbal has not been started yet!")
    self.serial_port.reset_input_buffer()
    r = False
    try:
        self.serial_port.write(bytes(f"{s}\n", 'utf-8'))
        time.sleep(0.01)
        r = self.serial_port.readline()
    finally:
        pass  # dummy statement to avoid empty 'finally' clause
    if r == False:
        logger.critical(f"Command failed: `{s}`!")
    return r

get_position Link

get_position()

Returns the pan & tilt positions of the Gimbal.

Source code in plantimager/blgimbal.py
150
151
152
153
def get_position(self) -> list:
    """Returns the pan & tilt positions of the Gimbal."""
    self.update_status()
    return self.p

get_status Link

get_status()

Returns the status of the custom board controlling the Gimbal.

Source code in plantimager/blgimbal.py
155
156
157
158
def get_status(self) -> str:
    """Returns the status of the custom board controlling the Gimbal."""
    self.update_status()
    return self.status

moveto Link

moveto(pan, tilt)

Move to a target position for pan & tilt.

Parameters:

Name Type Description Default
pan float

The desired pan orientation.

required
tilt float

The desired tilt orientation.

required
Source code in plantimager/blgimbal.py
180
181
182
183
184
185
186
187
188
189
190
191
192
def moveto(self, pan, tilt):
    """Move to a target position for pan & tilt.

    Parameters
    ----------
    pan : float
        The desired `pan` orientation.
    tilt : float
        The desired `tilt` orientation.
    """
    self.moveto_async(pan, tilt)
    self.wait()
    return None

moveto_async Link

moveto_async(pan, tilt)

Asynchronous move to a target position for pan & tilt.

Parameters:

Name Type Description Default
pan float

The desired pan orientation.

required
tilt float

The desired tilt orientation.

required
Source code in plantimager/blgimbal.py
194
195
196
197
198
199
200
201
202
203
204
205
def moveto_async(self, pan, tilt):
    """Asynchronous move to a target position for pan & tilt.

    Parameters
    ----------
    pan : float
        The desired `pan` orientation.
    tilt : float
        The desired `tilt` orientation.
    """
    self.set_target_pos(pan, tilt)
    return None

set_target_pos Link

set_target_pos(pan, tilt)

Set a target position for pan & tilt.

Parameters:

Name Type Description Default
pan float

The desired pan orientation.

required
tilt float

The desired tilt orientation.

required
Source code in plantimager/blgimbal.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def set_target_pos(self, pan, tilt):
    """Set a target position for pan & tilt.

    Parameters
    ----------
    pan : float
        The desired `pan` orientation.
    tilt : float
        The desired `tilt` orientation.
    """
    if self.invert_rotation:
        pan = -pan
    self.__send("X%d" % (self.zero_pan + int(pan / 360 * self.steps_per_turn)))
    if self.has_tilt:
        self.__send("Y%d" % (self.zero_tilt + int(tilt / 360 * self.steps_per_turn)))
    return None

start Link

start()

Start the serial connection with the custom board controlling the Gimbal.

Source code in plantimager/blgimbal.py
131
132
133
134
135
def start(self):
    """Start the serial connection with the custom board controlling the Gimbal."""
    self.serial_port = serial.Serial(self.port, self.baudrate, timeout=1, write_timeout=3)
    self.update_status()
    return None

stop Link

stop()

Stop the serial connection with the custom board controlling the Gimbal.

Source code in plantimager/blgimbal.py
137
138
139
140
141
142
def stop(self):
    """Stop the serial connection with the custom board controlling the Gimbal."""
    if self.serial_port:
        self.serial_port.close()
        self.serial_port = None
    return None

update_status Link

update_status()

Update the pan & tilt positions.

Source code in plantimager/blgimbal.py
207
208
209
210
211
212
213
214
215
def update_status(self):
    """Update the pan & tilt positions."""
    p = self.__send("p").decode('utf-8')
    logger.debug(f"Raw gimbal response: {p}")
    p = p.split(":")[-1].split(",")
    logger.debug(f"Gimbal response: pan={p[0]}, tilt={p[1]}")
    self.p[0] = (int(p[0]) - self.zero_pan) / self.steps_per_turn * 360
    self.p[1] = (int(p[1]) - self.zero_tilt) / self.steps_per_turn * 360
    return None