Exemplo n.º 1
0
def _decode_sensor_type(data):
    header = data[0]
    remaining_data = data[1:]

    # nob, number of offset bytes
    nob = (header & 0b11000000) >> 6
    if nob:
        error = "nob {} != 0 not allowed for Clair ERS sensors".format(nob)
        raise t.PayloadContentException(error)

    sensor_type = header & 0b00111111
    if sensor_type not in _DATA_DECODING_FUNCTIONS:
        raise t.PayloadContentException(
            "unsupported sensor type: {}".format(sensor_type))

    return (nob, sensor_type, remaining_data)
Exemplo n.º 2
0
def _decode_measurements(data: bytes):
    version, message_id, message_header, data = _decode_header(data)

    if version != 0:
        raise t.PayloadContentException("unsupported version number: {}".format(version))

    if message_id != 0:
        raise t.PayloadContentException("unsupported message id: {}".format(message_id))

    sample_count = message_header + 1

    measurements = _decode_sample_bytes(data)
    if len(measurements) != sample_count:
        raise t.PayloadContentException("incorrect sample count: {}".format(sample_count))

    return measurements
Exemplo n.º 3
0
def _decode_co2(data):
    if len(data) < 2:
        raise t.PayloadFormatException("less than two bytes to decode co2")

    co2_bytes = data[0:2]
    remaining_data = data[2:]

    co2_value = int.from_bytes(co2_bytes, byteorder='big')
    if co2_value > 10000:
        raise t.PayloadContentException("co2 {} > 10000".format(co2_value))

    co2 = t.CO2(co2_value)

    return (co2, remaining_data)
Exemplo n.º 4
0
def _decode_humidity(data):
    if not data:
        raise t.PayloadFormatException("no byte to decode relative humidity")

    humidity_value = data[0]
    remaining_data = data[1:]

    if humidity_value > 100:
        raise t.PayloadContentException(
            "relative humidity {} > 100".format(humidity_value))

    humidity = t.RelativeHumidity(humidity_value)

    return (humidity, remaining_data)
Exemplo n.º 5
0
def _decode_temperature(data):
    if len(data) < 2:
        raise t.PayloadFormatException(
            "less than two bytes to decode temperature")

    temperature_bytes = data[0:2]
    remaining_data = data[2:]

    temperature_value = int.from_bytes(
        temperature_bytes, byteorder='big', signed=True) / 10
    if temperature_value < -3276.5 or temperature_value > 3276.5:
        raise t.PayloadContentException(
            "temperature {} not in admissible range")

    temperature = t.Temperature(temperature_value)

    return (temperature, remaining_data)
Exemplo n.º 6
0
def _to_samples(measurements, rx_datetime):
    # Let's check the payload's content:
    # ERS payloads must contain CO2 measurements.
    # The number of CO2 measurements must be conforming to the payload spec.
    # They may contain temperature and humidity.
    # The number of temperature and humidity measurements is either 0 or equal
    # to the number of CO2 measurements.
    # All measurements are sorted in reverse-chronological order (LIFO).
    # The order within a CO2/temperature/humidity sample group does not matter.
    # This is ok: [C, T, H, H, T, C, T, H, C]
    # This is inadmissible: [C, C, C, T, T, T, H, H, H]
    sample_count = len([m for m in measurements if type(m) == t.CO2])
    valid_sample_counts = {
        ps.measurement_count
        for ps in PROTOCOL_PAYLOAD_SPECIFICATION.values()
    }
    if not sample_count in valid_sample_counts:
        raise t.PayloadContentException(
            "invalid number of measurements samples: {}".format(sample_count))

    # number of measurements per sample group, either 1 or 3
    n = int(len(measurements) / sample_count)
    if n != 1 and n != 3:
        raise t.PayloadContentException(
            "invalid number of measurements: {}".format(len(measurements)))

    # split measurements in sub lists of equal size n
    sample_groups = [
        measurements[i:i + n] for i in range(0, len(measurements), n)
    ]

    # check for exactly one CO2 measurement in each sample group
    if not all([
            len([m for m in sg if type(m) == t.CO2]) == 1
            for sg in sample_groups
    ]):
        raise t.PayloadContentException("invalid order of measurements")

    # check for either exactly one or zero temperature/humidity measurement
    t_counts = [
        len([m for m in sg if type(m) == t.Temperature])
        for sg in sample_groups
    ]
    h_counts = [
        len([m for m in sg if type(m) == t.RelativeHumidity])
        for sg in sample_groups
    ]
    if not ((all([c == 1 for c in t_counts]) and all([c == 1 for c in h_counts])) or \
            (all([c == 0 for c in t_counts]) and all([c == 0 for c in h_counts]))):
        raise t.PayloadContentException("invalid combination of measurements")

    rx_timestamp = round(rx_datetime.timestamp())
    # the measurement interval is determined by the number of samples per message
    measurement_interval = next(ps.measurement_interval for ps in PROTOCOL_PAYLOAD_SPECIFICATION.values() \
                                if ps.measurement_count == sample_count)

    samples = [
        t.Sample(
            timestamp=t.Timestamp(rx_timestamp - i * measurement_interval),
            co2=next(m for m in sg if type(m) == t.CO2),
            temperature=next((m
                              for m in sg if type(m) == t.Temperature), None),
            relative_humidity=next(
                (m for m in sg if type(m) == t.RelativeHumidity), None))
        for i, sg in enumerate(sample_groups)
    ]

    # return in chronological order
    samples.reverse()

    return samples