def camera_thread(configuration: Configuration, start_time: float): """Start the camera and log the video""" try: logging.info("Starting video capture") runtime_limit = configuration.get("runtime_limit") output_directory = configuration.get("output_directory") camera = picamera.PiCamera(framerate=90) camera.start_recording( os.path.join(output_directory, f"video_{int(start_time)}.h264")) camera.wait_recording(runtime_limit) camera.stop_recording() logging.info("Video capture complete") except Exception as ex: # pylint: disable=broad-except handle_exception("Video capture failure", ex)
def sensor_log_writing_loop(configuration: Configuration, start_time: float, data_queue: Queue): """Loop through clearing the data queue until RUNTIME_LIMIT has passed""" try: logging.info("Starting sensor log writing loop") runtime_limit = configuration.get("runtime_limit") output_directory = configuration.get("output_directory") with open( os.path.join(output_directory, f"sensor_log_{int(start_time)}.csv"), "w") as outfile: write_sensor_log(start_time, runtime_limit, outfile, data_queue) logging.info("Telemetry log writing loop complete") except Exception as ex: # pylint: disable=broad-except handle_exception("Telemetry log line writing failure", ex)
def init_gps(configuration: Configuration): """Initialize the serial port to receive GPS data""" gps_serial_port = configuration.get_device_configuration("gps", "serial_device") if not gps_serial_port: return None ser = serial.Serial(gps_serial_port, 9600, timeout=5.0) sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser)) return sio
def init_magnetometer_accelerometer(configuration: Configuration): """Initialize the sensor for magnetic and acceleration""" assignments = configuration.get_pin_assignments("lsm303") if not assignments: return None, None i2c = busio.I2C(assignments.get("scl"), assignments.get("sda")) mag = adafruit_lsm303dlh_mag.LSM303DLH_Mag(i2c) accel = adafruit_lsm303_accel.LSM303_Accel(i2c) return mag, accel
def __init__(self, configuration: Configuration, sql_file: str = "sessionstore.sqlite3"): """Create a new session store""" self.buffer = SafeBuffer() self.current_session = AtomicValue() self.output_dir = configuration.get("output_directory") self.sql_file = sql_file self.connection = None self.cursor = None
def test_write_queue_log(): outfile = io.StringIO("") data_queue = Queue() configuration = Configuration(None, dict(output_directory="./data")) while data_queue.qsize() < 10: data_queue.put((random.random(), random.random(), random.random())) write_queue_log(outfile, data_queue) contents = outfile.getvalue() assert contents assert len(contents.split("\n")) == 11
def main(): """Take a reading from each sensor to test its functionality""" # Load up the system configuration configuration = Configuration(os.getenv("AIR_CONFIG_FILE", None), Configuration.default_air_configuration) test_rfm9x(configuration) test_bmp3xx(configuration) test_lsm303dlh(configuration) test_gps(configuration)
def init_altimeter(configuration: Configuration): """Initialize the sensor for pressure, temperature, and altitude""" logging.info("Initializing altimeter") assignments = configuration.get_pin_assignments("bmp3xx") if not assignments: return None spi = busio.SPI(assignments.get("sck"), assignments.get("mosi"), assignments.get("miso")) cs = digitalio.DigitalInOut(assignments.get("cs")) # pylint: disable=invalid-name bmp = adafruit_bmp3xx.BMP3XX_SPI(spi, cs) return bmp
def telemetry_dashboard_server(configuration: Configuration, buffer_session_store: BufferSessionStore): """Serve the static parts of the dashboard visualization""" try: logging.info("Starting telemetry dashboard server") buffer_session_store.initialize() klass = ground_http_class_factory(buffer_session_store) httpd = HTTPServer(("0.0.0.0", configuration.get("http_server_port")), klass) httpd.serve_forever() except Exception as ex: # pylint: disable=broad-except logging.error("Telemetry dashboard server failure: %s", str(ex)) logging.exception(ex)
def init_altimeter(configuration: Configuration): """Initialize the sensor for pressure, temperature, and altitude""" try: logging.info("Initializing altimeter") assignments = configuration.get_pin_assignments("bmp3xx") if not assignments: return None i2c = busio.I2C(assignments.get("scl"), assignments.get("sda")) bmp = adafruit_bmp3xx.BMP3XX_I2C(i2c) bmp._wait_time = 0 # pylint: disable=protected-access return bmp except Exception as ex: # pylint: disable=broad-except bmp = DummyBMP() logging.exception(ex) return bmp
def init_radio(configuration: Configuration): """Initialize the radio""" logging.info("Initializing transmitter") assignments = configuration.get_pin_assignments("rfm9x") if not assignments: return None spi = busio.SPI( assignments.get("sck"), MOSI=assignments.get("mosi"), MISO=assignments.get("miso"), ) cs = DigitalInOut(assignments.get("cs")) # pylint: disable=invalid-name reset = DigitalInOut(assignments.get("reset")) rfm9x = adafruit_rfm9x.RFM9x(spi, cs, reset, 915.0, baudrate=10000000) rfm9x.tx_power = 23 return rfm9x
def init_magnetometer_accelerometer(configuration: Configuration): """Initialize the sensor for magnetic and acceleration""" try: logging.info("Initializing magnetometer/accelerometer") assignments = configuration.get_pin_assignments("lsm303") if not assignments: return None, None i2c = busio.I2C(assignments.get("scl"), assignments.get("sda")) mag = adafruit_lsm303dlh_mag.LSM303DLH_Mag(i2c) accel = adafruit_lsm303_accel.LSM303_Accel(i2c) return mag, accel except Exception as ex: # pylint: disable=broad-except mag = DummyMag() accel = DummyAccel() logging.exception(ex) return mag, accel
def init_reset_button(configuration: Configuration, continue_running: AtomicValue): """Setup a listener for reset""" def handle_reset_button(_): logging.info("Reset button pressed!") continue_running.update(False) GPIO.setmode(GPIO.BCM) # pylint: disable=no-member channel = int(configuration.get_device_configuration("reset", "pin")) GPIO.setup( # pylint: disable=no-member channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN # pylint: disable=no-member ) GPIO.add_event_detect( # pylint: disable=no-member channel, GPIO.RISING, # pylint: disable=no-member callback=handle_reset_button, bouncetime=500, # pylint: disable=no-member )
def test_buffer_session_store(): configuration = Configuration(None, dict(output_directory="./data")) store = BufferSessionStore(configuration, f"test_sessionstore_{time.time()}.sql") store.initialize() first_session = store.current_session.get_value() assert first_session >= int(time.time()) store.buffer.append(random.random()) time.sleep(1) store.create_new_session() next_session = store.current_session.get_value() assert next_session >= int(time.time()) assert next_session != first_session assert store.buffer.size() == 0 time.sleep(1) store.create_new_session() sessions = store.get_sessions() assert sessions == [next_session, first_session]
def camera_thread( configuration: Configuration, start_time: float, continue_running: AtomicValue, continue_logging: AtomicValue, ): """Start the camera and log the video""" try: logging.info("Starting video capture") output_directory = configuration.get("output_directory") camera = picamera.PiCamera(framerate=90) camera.start_recording( os.path.join(output_directory, f"video_{int(start_time)}.h264") ) while continue_running.get_value() and continue_logging.get_value(): time.sleep(5) camera.stop_recording() logging.info("Video capture complete") except Exception as ex: # pylint: disable=broad-except handle_exception("Video capture failure", ex)
def sensor_log_writing_loop( configuration: Configuration, start_time: float, data_queue: Queue, continue_running: AtomicValue, continue_logging: AtomicValue, ): """Loop through clearing the data queue until cancelled""" try: logging.info("Starting sensor log writing loop") output_directory = configuration.get("output_directory") with open( os.path.join(output_directory, f"sensor_log_{int(start_time)}.csv"), "w", encoding="utf8", ) as outfile: write_sensor_log( start_time, outfile, data_queue, continue_running, continue_logging ) logging.info("Telemetry log writing loop complete") except Exception as ex: # pylint: disable=broad-except handle_exception("Telemetry log line writing failure", ex)
def telemetry_streaming_server(configuration: Configuration, buffer_session_store: BufferSessionStore): """Serve the active buffer over websocket""" async def data_stream(websocket, _): """Handle a connection on a websocket""" try: logging.info("Client connected to streaming server") last_index = 0 while True: if websocket.closed: return end_index = buffer_session_store.buffer.size() if end_index > last_index: await websocket.send( json.dumps( buffer_session_store.buffer.get_range( last_index, end_index))) last_index = end_index await asyncio.sleep(1) except websockets.exceptions.ConnectionClosed: logging.info("Client disconnected from streaming server") except Exception as ex: # pylint: disable=broad-except logging.error("Telemetry streaming server failure: %s", str(ex)) logging.exception(ex) try: logging.info("Starting telemetry streaming server") loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) start_server = websockets.serve( data_stream, "0.0.0.0", configuration.get("streaming_server_port")) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() except websockets.exceptions.ConnectionClosed: logging.info("Client disconnected from streaming server") except Exception as ex: # pylint: disable=broad-except logging.error("Telemetry streaming server failure: %s", str(ex)) logging.exception(ex)
def main(): """Inboard data capture and transmission script""" # Load up the system configuration configuration = Configuration( os.getenv("AIR_CONFIG_FILE", None), Configuration.default_air_configuration ) # Queue to manage data synchronization between sensor reading, transmission, and data logging data_queue = Queue() # Timestamp to use for log files and log saving cutoff start_time = time.time() # Thread safe place to store altitude reading current_reading = AtomicValue() # Holds the most recent GPS data gps_value = AtomicValue((0.0, 0.0, 0.0, 0.0)) altimeter_value = AtomicValue() altimeter_value.update((0.0, 0.0)) magnetometer_accelerometer_value = AtomicValue() magnetometer_accelerometer_value.update((0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) gps_thread = create_gps_thread(configuration, gps_value) gps_thread.start() write_thread = Thread( target=sensor_log_writing_loop, args=(configuration, start_time, data_queue), daemon=True, ) write_thread.start() camera_thread_handle = Thread( target=camera_thread, args=(configuration, start_time), daemon=True, ) camera_thread_handle.start() transmitter_thread_handle = Thread( target=transmitter_thread, args=( configuration, start_time, current_reading, ), daemon=True, ) transmitter_thread_handle.start() altimeter_thread = Thread( target=altimeter_reading_loop, args=(configuration, altimeter_value) ) altimeter_thread.start() magnetometer_accelerometer_thread = Thread( target=magnetometer_accelerometer_reading_loop, args=(configuration, magnetometer_accelerometer_value), ) magnetometer_accelerometer_thread.start() sensor_reading_loop( start_time, data_queue, current_reading, gps_value, altimeter_value, magnetometer_accelerometer_value, )
def main(): """Inboard data capture and transmission script""" # Load up the system configuration configuration = Configuration(os.getenv("AIR_CONFIG_FILE", None), Configuration.default_air_configuration) # Queue to manage data synchronization between sensor reading, transmission, and data logging data_queue = Queue(1000) # Timestamp to use for log files and log saving cutoff start_time = time.time() # Thread safe place to store altitude reading current_readings = AtomicBuffer(50) # Holds the most recent GPS data gps_value = AtomicValue((0.0, 0.0, 0.0, 0.0)) # pcnt counter to runtime limit pcnt_to_limit = AtomicValue(0.0) # Thread safe place to store continue value continue_running = AtomicValue(True) # Thread safe place to store continue value continue_logging = AtomicValue(True) # Setup listener for reset button init_reset_button(configuration, continue_running) gps_thread = create_gps_thread(configuration, gps_value, continue_running) gps_thread.start() write_thread = Thread( target=sensor_log_writing_loop, args=( configuration, start_time, data_queue, continue_running, continue_logging, ), daemon=True, ) write_thread.start() camera_thread_handle = Thread( target=camera_thread, args=(configuration, start_time, continue_running, continue_logging), daemon=True, ) camera_thread_handle.start() transmitter_thread_handle = Thread( target=transmitter_thread, args=( configuration, start_time, current_readings, pcnt_to_limit, continue_running, ), daemon=True, ) transmitter_thread_handle.start() sensor_reading_thread = Thread( target=sensor_reading_loop, args=( configuration, start_time, data_queue, current_readings, gps_value, continue_running, ), daemon=True, ) sensor_reading_thread.start() runtime_limit = configuration.get("runtime_limit") while continue_running.get_value( ) and time.time() - start_time <= runtime_limit: pcnt_to_limit.update((time.time() - start_time) / runtime_limit) time.sleep(1) logging.info("Stopping write activities") continue_logging.update(False) write_thread.join() camera_thread_handle.join() pcnt_to_limit.update(1) logging.info("Write activities ended") gps_thread.join() transmitter_thread_handle.join() sensor_reading_thread.join()