CONTROL SYSTEM
The
Server
Our server was built and programmed as the centerpiece of the control architecture that encompasses the bioreactor and measurement
system of
The server code controls the interaction modules, the measurement chambers and the communication with the robot in the arena. It listens for commands that are sent by either the robot or the client software and reacts by executing the following functions.
Directly available
-Server functions activated by commands sent from client:
-
heat
Starts the heating module for 5 seconds. -
pump_1
Start all pumps for 11 seconds (the length of one measure cycle) to flush the system and fill it with fresh suspension from the reactor. -
temp
Get the current temperature reading from the themperature sensor in the Temperature Interaction Module. -
cam
Take a testpicture with the master camera and report the measurement result. -
drive
Send out adrive
command to the Robot to test range and connection. -
turn
Send out aturn
command to the Robot to calibrate the radius.
In the case of a real experiment run in the temperature setup, the robot sends one of the following messages to the server according to its position and orientation in the arena and the sensor input it perceives. The server reacts by initiating the corresponding processes as described below.
Robot - Server Interaction :
-
clear
The server starts a waiting loop to cool down the potentially still hot heating chamber to prevent tainting the current run by previous runs. After this cool-down period, fresh suspension is pumped into the measurement chamber and a baseline measurement is conducted. To prevent any unknown or outside influences, another waiting loop, identical in duration to the activation heating period during a run with activated temperature interaction, is started now. Finally, the suspension is pumped into the measurement chamber and a measurement is taken. The baseline measurement and actual measurement data is now compared and in case the difference surpasses a given threshold, the server sends the
turn
command to the robot. Otherwisedrive
sent to the robot to signal an open path ahead. -
obstacle
The server starts with a cool-down period to prevent tainted measuring results. A baseline measurement is made with fresh suspension. Fresh suspension is pumped into the temperature interaction module and heated to target temperature. The heated and activated suspension is pumped into the measurement chamber and a measurement is taken and compared to the baseline value. If the baseline and the actual measurement have a difference equal or greater than the given threshold, the server sends the
turn
command to the robot. Otherwisedrive
sent to the robot to signal an open path ahead.
The
Client
To be able to test all of the control systems functionality (pump control, heating, measurements) without having
to actually run the full experiments, a small command line application was written in Python3. Intended to being
run on any computer in the same network as the
The commands are sent and answers are received over UDP, a common data transfer protocol. Although not reliable for critical communication because of missing error checking and guaranteed order of the packets arriving, for the small data packets we are sending here it is sufficient.
The client code for testing the system functionality:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #!/usr/sbin/python3 # ColiBot Client # - # A minimal udp messaging client to send (test) messages to the ColiBot server. # import socket import sys # create datagram udp socket try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) except socket.error: print('Failed to create socket') sys.exit() host = '192.168.0.4' port = 4343 while(1): msg = input('Enter message to send : ') try: # Send string sock.sendto(str.encode(msg), (host, port)) # receive data from client (data, addr) d = sock.recvfrom(1024) reply = d[0] addr = d[1] print('Server reply : ' + str(reply)) if (reply.decode("utf-8") == 'turn'): print("Got 'turn' message.") if (reply.decode("utf-8") == 'drive'): print("Got 'drive' message.") except socket.error as e: print('Socket Error: {}'.format(e)) sock.close() sys.exit() except KeyboardInterrupt: sock.close() sys.exit() |
The server code for controlling the bioreactor and measurement system:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | #! /usr/bin/python3 # Simple udp socket server import calendar import datetime import logging import socket import sys import time import RPi.GPIO as GPIO import serial from cam.colicam_master import ColiCamMaster from cam.colicam_slave import ColiCamSlave from w1thermsensor import W1ThermSensor # heater pins HEAT = 40 PORT = 4343 # Arbitrary non-privileged port HOST = "192.168.43.150" # Messages OBSTACLE = 'obstacle' CLEAR = 'clear' HEATING_TEST = 'heat' GET_TEMP = 'temp' PUMP_1_TEST = 'pump_1' CAM_TEST = 'cam' TURN = 'turn' DRIVE = 'drive' # heating HEATING = False TARGET_TEMP = 43 # degrees INTERVALL = 8 # seconds HEATING_PERIOD = 300 # seconds # pump PUMP_IVALL = 12 DIFF_THRESHOLD = 12 logger = logging.getLogger('colibot') def get_temperature(): """ read temperature from sensor """ sensor = W1ThermSensor() temp_c = sensor.get_temperature() return temp_c def toggle_heating(heat): """ GPIO 20 and 21 HIGH = heating on LOW = heating off """ if heat is False: #logger.info("HEATING OFF") HEATING = False GPIO.output(HEAT, 0) else: #logger.info("HEATING ON") HEATING = True GPIO.output(HEAT, 1) def start_heating(): """ """ logger.info("*** Entering heating loop ...") while(1): temp = get_temperature() logger.info("*** Current temperature: " + str(temp)) if temp >= (TARGET_TEMP - 2): logger.info("*** TARGET_TEMP reached!") break; toggle_heating(True) time.sleep(15) toggle_heating(False) heating_time = HEATING_PERIOD # seconds start = calendar.timegm(time.gmtime()) end = start + heating_time logger.info("*** activation period, keeping target temp for 5 mins:") remaining_time = heating_time while(1): # logger.info("minutes left: "+str( (end - calendar.timegm(time.gmtime()))/60 ) ) if calendar.timegm(time.gmtime()) >= end: break temp = get_temperature() # logger.info("-->") logger.info("*** Current temperature: " + str(temp)) if temp >= (TARGET_TEMP ): time.sleep(10) elif( temp < TARGET_TEMP ): # logger.info("*** heating ...") toggle_heating(True) time.sleep(5) toggle_heating(False) #if remaining_time % 60 == 0: logger.info("> {} mins remaining.".format( remaining_time / 60 )) time.sleep(5) remaining_time -= 14 def start_pump(pwms, seconds): """ pump_1: pwms[0](11, 180) pin 13 pin 15 pump_2: pwms[1](19, 180) pin 21 pin 23 --> ~1ml per second """ logger.info("*** Starting pumps for {} seconds.".format(str(seconds))) pwms[0].start(100) # out pwms[1].start(100) # in time.sleep(seconds) logger.info("*** Stopping pumps") pwms[0].stop() pwms[1].stop() def take_measure(): logger.info("*** Measuring ...") result = cam.measure() return result; def diff_results(bl, m): r_diff = abs(bl[0] - m[0]) g_diff = abs(bl[1] - m[1]) b_diff = abs(bl[2] - m[2]) diff = r_diff + g_diff + b_diff logger.info("*** Diff_results: r_diff={} g_diff={} b_diff={}".format(r_diff, g_diff, b_diff)) logger.info("*** total: {}".format(diff)) if(diff >= DIFF_THRESHOLD): return True else: return False def create_socket(): # Datagram (udp) socket try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) logger.info('Socket created') except socket.error as msg: logger.error('Failed to create socket.') GPIO.cleanup() sys.exit() # Bind socket to local host and port try: sock.bind((HOST, PORT)) except socket.error as msg: logger.error('Bind failed. ') GPIO.cleanup() sys.exit() logger.info('Socket bind complete') return sock def start_listening(sock, pwms): # now keep talking with the client try: while 1: # receive data from client (data, addr) rec = sock.recvfrom(1024) data = rec[0] addr = rec[1] logger.info("***") if data.decode("utf-8") == OBSTACLE: # 'obstacle' logger.info( "recieved OBSTACLE: requesting colis and heating them to TARGET_TEMP...") # wait for some time to cool of heating chamber cooldown = 180 logger.info("Waiting {} to cool chamber.".format(cooldown/60)) time.sleep(cooldown) # fill chamber for baseline measureing start_pump(pwms, PUMP_IVALL) # baseline baseline = take_measure() logger.info("baseline values: r{}, g{}, b{}".format(baseline[0], baseline[1], baseline[2])) start_heating() # activated to chamber start_pump(pwms, 5) logger.info("... start analyzing") measurement = take_measure() if (diff_results(baseline, measurement) is True): logger.info(" TURN !!.") reply = 'turn' else: logger.info(" DRIVE !!.") reply = 'drive' elif data.decode("utf-8") == CLEAR: # 'clear' logger.info("recieved CLEAR: requesting colis and start analyzing.") # wait for some time to cool of heating chamber cooldown = 180 logger.info("Waiting {} to cool chamber.".format(cooldown/60)) time.sleep(cooldown) # fill chamber for baseline measureing start_pump(pwms, PUMP_IVALL) # baseline baseline = take_measure() logger.info("baseline values: r{}, g{}, b{}".format(baseline[0], baseline[1], baseline[2])) logger.info("waiting loop: {} minutes.".format(HEATING_PERIOD/60)) time.sleep(HEATING_PERIOD) start_pump(pwms, 5) logger.info("... start analyzing") measurement = take_measure() if (diff_results(baseline, measurement) is True): logger.info(" TURN !!") reply = 'turn' else: reply = 'drive' elif data.decode("utf-8") == HEATING_TEST: # 'heat' logger.info("received HEATING_TEST") reply = 'OK: HEATING TEST for 5 seconds ...' toggle_heating(True) time.sleep(5) toggle_heating(False) elif data.decode("utf-8") == PUMP_1_TEST: # 'pump_1' logger.info("received PUMP_1_TEST") reply = 'OK: PUMPING TEST for 11 seconds (measure cycle)' start_pump(pwms, 11) elif data.decode("utf-8") == GET_TEMP: # 'temp' logger.info("received GET_TEMP") temp = get_temperature() reply = 'OK: temperature is ' + \ str(temp) + ' degree Celsius ...' elif data.decode("utf-8") == CAM_TEST: # 'cam' logger.info("received CAM_TEST") result = cam.measure() reply = 'OK: Took pic, result = ' + str(result) cam.exit_clean() elif data.decode("utf-8") == DRIVE: # 'drive' logger.info("received DRIVE") reply = 'drive' elif data.decode("utf-8") == TURN: # 'turn' logger.info("received TURN") reply = 'turn' else: logger.info("invalid message") reply = 'FAIL: ... invalid message: [' + \ data.decode("utf-8") + ']' sock.sendto(reply.encode("UTF-8"), addr) logger.info("Sent message.") # logger.info('Message[' + addr[0] + ':' + # str(addr[1]) + '] - ' + data.decode("utf-8")) except KeyboardInterrupt: logger.info("Quitting.") if HEATING: toggle_heating(False) sock.close() GPIO.cleanup() cam.exit_clean() def main(): # format FORMAT = "[%(asctime)s %(funcName)18s()] %(message)s" fileHandler = logging.FileHandler('log.txt') fileHandler.setFormatter(logging.Formatter(FORMAT)) logger.addHandler(fileHandler) logging.basicConfig(level=20, format=FORMAT, datefmt='%d.%m.%Y %H:%M:%S') logger.info("Start logging") # setup GPIO GPIO.setmode(GPIO.BOARD) GPIO.setup(HEAT, GPIO.OUT) GPIO.output(HEAT, 0) #pwm1 GPIO.setup(11, GPIO.OUT) GPIO.setup(13, GPIO.OUT) GPIO.setup(15, GPIO.OUT) pwm1 = GPIO.PWM(11, 220) GPIO.output(13, 0) GPIO.output(15, 1) #pwm2 GPIO.setup(29, GPIO.OUT) GPIO.setup(31, GPIO.OUT) GPIO.setup(33, GPIO.OUT) pwm2 = GPIO.PWM(29, 220) GPIO.output(31, 0) GPIO.output(33, 1) pwms = [pwm1, pwm2] logger.info("###############################") logger.info(" ColiBot Thermo udp_server ") logger.info("-------------------------------") sock = create_socket() logger.info("-------------------------------") logger.info("... start listening ...") start_listening(sock, pwms) cam = ColiCamMaster() ColiCamSlave() if __name__ == "__main__": main() |