Example #1
0
def write_sensor_log(
    start_time: float,
    outfile,
    data_queue: Queue,
    continue_running: AtomicValue,
    continue_logging: AtomicValue,
):
    """Write the queue to the log until told to stop"""
    lines_written = 0
    last_queue_check = time.time()
    while continue_running.get_value() and continue_logging.get_value():
        try:
            new_lines_written = write_queue_log(outfile, data_queue, 300)
            if new_lines_written > 0:
                lines_written += new_lines_written
                if last_queue_check + 10.0 < time.time():
                    last_queue_check = time.time()
                    elapsed = last_queue_check - start_time
                    logging.info(
                        "Lines written: %d in %s seconds with %d ready",
                        lines_written,
                        elapsed,
                        data_queue.qsize(),
                    )
            time.sleep(7)
        except Exception as ex:  # pylint: disable=broad-except
            handle_exception("Telemetry log line writing failure", ex)
Example #2
0
def test_gps(configuration: Configuration):
    """Take a reading from the gps to test its functionality"""
    try:
        logging.info("Testing gps ...")
        gps = init_gps(configuration)
        if gps:
            gps_value = AtomicValue()
            start_time = time.time()
            readings = 0
            while readings < 10 or time.time() - start_time < TEST_TIME_LENGTH:
                try:
                    if take_gps_reading(gps, gps_value):
                        readings += 1
                except Exception as ex:  # pylint: disable=broad-except:
                    handle_exception("Reading failure", ex)
            total_time = time.time() - start_time
            logging.info(
                "Last GPS reading: %f, %f, %f, %f out of %d readings at an average rate of %f/sec",
                *gps_value.get_value(),
                readings,
                float(readings) / total_time,
            )
        else:
            logging.info("No GPS hardware configured")
    except Exception as ex:  # pylint: disable=broad-except
        handle_exception("GPS failure", ex)
Example #3
0
def test_atomic_value_try_update_fail():
    raw_value = random.random()
    value = AtomicValue(raw_value)
    value.lock.acquire()
    assert not value.try_update(random.random())
    value.lock.release()
    assert raw_value == value.get_value()
Example #4
0
def test_atomic_value_try_update_succeed():
    raw_value = random.random()
    value = AtomicValue(raw_value)
    value.lock.acquire()
    value.lock.release()
    assert value.try_update(random.random())
    assert raw_value != value.get_value()
Example #5
0
def test_digest_next_ground_reading():
    values = [random.random() for _ in range(TELEMETRY_TUPLE_LENGTH)]
    rfm9x = MockRFM9x(values)
    new_data_queue = Queue()
    gps_value = AtomicValue([random.random() for _ in range(4)])
    digest_next_ground_reading(rfm9x, new_data_queue, gps_value)
    assert new_data_queue.qsize() == 1
    expected = (*values, rfm9x.last_rssi, *gps_value.get_value())
    assert new_data_queue.get() == expected
Example #6
0
 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
Example #7
0
def altimeter_reading_loop(configuration: Configuration,
                           altimeter_value: AtomicValue):
    """Continually read the altimeter and set it to the atomic var"""
    try:
        bmp = init_altimeter(configuration)
        while bmp:
            try:
                altimeter_value.update(bmp._read())  # pylint: disable=protected-access
            except Exception as ex:  # pylint: disable=broad-except
                handle_exception("Altimeter reading failure", ex)
    except Exception as ex:  # pylint: disable=broad-except
        handle_exception("Altimeter setup failure", ex)
Example #8
0
class BufferSessionStore:
    """Data capture session storage engine"""
    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 initialize(self):
        """Initialize the store. (Pins the SQLite connection to the calling thread)"""
        self.connection = sqlite3.connect(
            path.join(self.output_dir, self.sql_file))
        self.cursor = self.connection.cursor()
        self.cursor.execute(
            "CREATE TABLE IF NOT EXISTS sessions (timestamp NUMBER UNIQUE NOT NULL)"
        )
        self.connection.commit()
        self.create_new_session()

    def get_sessions(self) -> List[float]:
        """Get a list of saved sessions"""
        self.cursor.execute(
            "SELECT timestamp FROM sessions WHERE timestamp != ? ORDER BY timestamp DESC",
            (self.current_session.get_value(), ),
        )
        return [row[0] for row in self.cursor.fetchall()]

    def create_new_session(self) -> float:
        """Create and save a new session"""
        timestamp = int(time.time())
        self.current_session.update(timestamp)
        self.cursor.execute("INSERT INTO sessions (timestamp) VALUES (?)",
                            (timestamp, ))
        self.connection.commit()
        self.buffer.purge()
        return timestamp

    def load_session(self, session) -> List[List[float]]:
        """Load data from a given session"""
        with open(self.data_path_for_session(session), "r") as csvfile:
            reader = csv.reader(csvfile)
            return [[float(v) for v in r] for r in reader]

    def data_path_for_session(self, session=None) -> str:
        """Get the path to the given session's data"""
        if not session:
            session = self.current_session.get_value()
        return path.join(self.output_dir, f"telemetry_log_{session}.csv")
Example #9
0
def take_gps_reading(sio, gps_value: AtomicValue) -> bool:
    """Grab the most recent data from GPS feed"""
    line = sio.readline()
    if line[0:6] == "$GPGGA":
        gps = pynmea2.parse(line)
        gps_value.update((
            gps.latitude if gps else 0.0,
            gps.longitude if gps else 0.0,
            gps.gps_qual if gps else 0.0,
            float(gps.num_sats) if gps else 0.0,
        ))
        return True
    return False
Example #10
0
def magnetometer_accelerometer_reading_loop(
        configuration: Configuration,
        magnetometer_accelerometer_value: AtomicValue):
    """Continually read the accelerometer/magnetometer and set it to the atomic var"""
    try:
        mag, accel = init_magnetometer_accelerometer(configuration)
        while mag and accel:
            try:
                magnetometer_accelerometer_value.update(
                    (*accel.acceleration, *mag.magnetic))
            except Exception as ex:  # pylint: disable=broad-except
                handle_exception("Magnetometer/Accelerometer reading failure",
                                 ex)
    except Exception as ex:  # pylint: disable=broad-except
        handle_exception("Magnetometer/Accelerometer setup failure", ex)
Example #11
0
def take_gps_reading(sio, gps_value: AtomicValue) -> bool:
    """Grab the most recent data from GPS feed"""
    line = sio.readline()
    gps = pynmea2.parse(line)
    if isinstance(gps, pynmea2.types.talker.GGA):
        gps_value.update(
            (
                gps.latitude if gps else 0.0,
                gps.longitude if gps else 0.0,
                float(gps.gps_qual) if gps else 0.0,
                float(gps.num_sats) if gps else 0.0,
            )
        )
        return True
    return False
Example #12
0
def transmitter_thread(
    configuration: Configuration,
    start_time: float,
    current_readings: AtomicBuffer,
    pcnt_to_limit: AtomicValue,
    continue_running: AtomicValue,
):
    """Transmit the latest data"""
    try:
        rfm9x = init_radio(configuration)
        if not rfm9x:
            return
        last_check = time.time()
        readings_sent = 0
        while continue_running.get_value():
            try:
                readings_sent, last_check = transmit_latest_readings(
                    pcnt_to_limit,
                    rfm9x,
                    last_check,
                    readings_sent,
                    start_time,
                    current_readings,
                )
                time.sleep(0)
            except Exception as ex:  # pylint: disable=broad-except
                handle_exception("Transmitter failure", ex)
    except Exception as ex:  # pylint: disable=broad-except
        handle_exception("Transmitter failure", ex)
Example #13
0
def transmit_latest_readings(
    pcnt_to_limit: AtomicValue,
    rfm9x,
    last_check: float,
    readings_sent: int,
    start_time: float,
    current_readings: AtomicBuffer,
) -> Tuple[int, float]:
    """Get the latest value from the sensor store and transmit it as a byte array"""
    infos = current_readings.read()
    if len(infos) < 2:
        return readings_sent, last_check
    info1 = infos[0]
    info2 = infos[int(math.ceil(len(infos) / 2))]
    if not info1 or not info2:
        return readings_sent, last_check
    info = (*info1, *info2)
    clean_info = [float(i) for i in info]
    encoded = struct.pack(
        "d" + TELEMETRY_STRUCT_STRING + TELEMETRY_STRUCT_STRING,
        *(pcnt_to_limit.get_value(), *clean_info)
    )
    current_readings.clear()
    logging.debug("Transmitting %d bytes", len(encoded))
    rfm9x.send(encoded)
    readings_sent += 1
    if last_check > 0 and last_check + 10.0 < time.time():
        last_check = time.time()
        logging.info(
            "Transmit rate: %f/s",
            float(readings_sent) / float(last_check - start_time),
        )
    return readings_sent, last_check
Example #14
0
def test_rfm9x(configuration: Configuration):
    """Transmit on the rfm9x to test its functionality"""
    try:
        logging.info("Testing rfm9x ...")
        rfm9x = init_radio(configuration)
        if rfm9x:
            camera_is_running = AtomicValue(0.0)
            current_reading = AtomicBuffer(2)
            current_reading.put([0.0 for _ in range(TELEMETRY_TUPLE_LENGTH)])
            current_reading.put([0.0 for _ in range(TELEMETRY_TUPLE_LENGTH)])
            start_time = time.time()
            transmissions = 0
            while time.time() - start_time < TEST_TIME_LENGTH:
                transmit_latest_readings(camera_is_running, rfm9x, 0, 0, 0,
                                         current_reading)
                transmissions += 1
            total_time = time.time() - start_time
            logging.info(
                "Transmitted %d messages at an average rate of %f/sec",
                transmissions,
                float(transmissions) / total_time,
            )
        else:
            logging.info("No rfm9x hardware configured")
    except Exception as ex:  # pylint: disable=broad-except
        handle_exception("rfm9x failure", ex)
Example #15
0
def test_write_sensor_log():
    start_time = time.time()
    outfile = io.StringIO("")
    data_queue = Queue()
    while data_queue.qsize() < 100:
        data_queue.put((random.random(), random.random(), random.random()))
    continue_running = AtomicValue(True)
    thread = Thread(
        target=write_sensor_log,
        args=(start_time, outfile, data_queue, continue_running,
              continue_running),
    )
    thread.start()
    time.sleep(5)
    continue_running.update(False)
    thread.join()
    contents = outfile.getvalue()
    assert contents
    assert len(contents.split("\n")) > 0
Example #16
0
def gps_reception_loop(sio, gps_value: AtomicValue, continue_running: AtomicValue):
    """Loop forever reading GPS data and passing it to an atomic value"""
    if not sio:
        return
    while continue_running.get_value():
        try:
            take_gps_reading(sio, gps_value)
        except Exception as ex:  # pylint: disable=broad-except
            handle_exception("GPS reading failure", ex)
        time.sleep(0)
Example #17
0
def digest_next_sensor_reading(
    start_time: float,
    data_queue: Queue,
    current_reading: AtomicValue,
    gps_value: AtomicValue,
    altimeter_value: AtomicValue,
    magnetometer_accelerometer_value: AtomicValue,
) -> float:
    """Grab the latest values from all sensors and put the data in the queue and atomic store"""
    now = time.time()
    info = (
        now - start_time,
        *altimeter_value.get_value(),
        *magnetometer_accelerometer_value.get_value(),
        *gps_value.get_value(),
    )
    data_queue.put(info)
    current_reading.try_update(info)
    return now
Example #18
0
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)
Example #19
0
def sensor_reading_loop(
    configuration: Configuration,
    start_time: float,
    data_queue: Queue,
    current_readings: AtomicBuffer,
    gps_value: AtomicValue,
    continue_running: AtomicValue,
):
    """Read from the sensors on and infinite loop and queue it for transmission and logging"""
    try:
        logging.info("Starting sensor measurement loop")
        bmp = init_altimeter(configuration)
        mag, accel = init_magnetometer_accelerometer(configuration)
        start = time.time()
        readings = 0.0
        while continue_running.get_value():
            try:
                digest_next_sensor_reading(
                    start_time,
                    data_queue,
                    current_readings,
                    gps_value.get_value(),
                    bmp._read(),  # pylint: disable=protected-access
                    (*accel.acceleration, *mag.magnetic),
                )
                readings += 1.0
                now = time.time()
                if now - start > 10:
                    readings_per_second = readings / (now - start)
                    logging.info("Sample rate: %f/s", readings_per_second)
                    start = time.time()
                    readings = 0
            except Exception as ex:  # pylint: disable=broad-except
                handle_exception("Telemetry measurement point reading failure", ex)
    except Exception as ex:  # pylint: disable=broad-except
        handle_exception("Telemetry measurement point reading failure", ex)
Example #20
0
def test_transmit_latest_readings():
    last_check = 1
    readings_sent = 0
    start_time = time.time()
    rfm9x = MockRFM9X()
    current_reading = AtomicValue(
        [random.random() for _ in range(TELEMETRY_TUPLE_LENGTH)]
    )
    readings_sent_1, last_check_1 = transmit_latest_readings(
        rfm9x, last_check, readings_sent, start_time, current_reading
    )
    assert readings_sent_1 > readings_sent
    assert last_check < last_check_1
    assert last_check_1 <= time.time()
    assert len(rfm9x.sent) == (TELEMETRY_TUPLE_LENGTH * 8)
Example #21
0
def transmit_latest_readings(
    rfm9x,
    last_check: float,
    readings_sent: int,
    start_time: float,
    current_reading: AtomicValue,
) -> Tuple[int, float]:
    """Get the latest value from the sensor store and transmit it as a byte array"""
    info = current_reading.get_value()
    if info:
        clean_info = [float(i) for i in info]
        encoded = struct.pack(TELEMETRY_STRUCT_STRING, *clean_info)
        logging.debug("Transmitting %d bytes", len(encoded))
        rfm9x.send(encoded)
        readings_sent += 1
        if last_check > 0 and last_check + 10.0 < time.time():
            last_check = time.time()
            logging.info("Readings sent: %d / %d seconds", readings_sent,
                         last_check - start_time)
    return readings_sent, last_check
Example #22
0
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()
Example #23
0
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,
    )
Example #24
0
def test_atomic_value():
    raw_value = random.random()
    value = AtomicValue(raw_value)
    assert raw_value == value.get_value()
    value.update(random.random())
    assert raw_value != value.get_value()
Example #25
0
def test_digest_next_sensor_reading():
    start_time = time.time()
    altimeter_value = AtomicValue([random.random() for _ in range(2)])
    gps_value = AtomicValue([random.random() for _ in range(4)])
    magnetometer_accelerometer_value = AtomicValue([random.random() for _ in range(6)])
    data_queue = Queue()
    current_reading = AtomicValue()
    now = digest_next_sensor_reading(
        start_time,
        data_queue,
        current_reading,
        gps_value,
        altimeter_value,
        magnetometer_accelerometer_value,
    )
    logged = data_queue.get()
    expected_tuple = (
        now - start_time,
        *altimeter_value.get_value(),
        *magnetometer_accelerometer_value.get_value(),
        *gps_value.get_value(),
    )
    assert logged
    assert logged == expected_tuple
    assert current_reading.get_value() == expected_tuple
    assert len(logged) == TELEMETRY_TUPLE_LENGTH
Example #26
0
def test_take_gps_reading():
    line = f"$GPGGA,134658.00,5106.9792,N,11402.3003,W,2,09,1.0,1048.47,M,-16.27,M,08,AAAA*60"
    sio = MockSerial(line)
    val = AtomicValue()
    take_gps_reading(sio, val)
    assert val.get_value() == (51.11632, -114.03833833333333, 2, 9)