from bluetooth import BLE
import bme680
import display
import leds
import power
from struct import pack
import time


DEVICE_NAME = b"card10"  # 9 characters max.
INTERVAL = 3  # Don't set this below 3.
LED_FLASH_DURATION = 20
BATTERY_WARN = 20  # At what percentage to start warning about battery power.


def adv_element(typ: int, content: bytes) -> bytes:
    size = len(content) + 1
    return pack("<BB", size, typ) + content


def adv_service_data(uuid: int, content: bytes) -> bytes:
    return adv_element(0x16, pack("<H", uuid) + content)


def sensor_data(reading, bat_p):
    bsec = isinstance(reading, bme680.BSECData)
    print(reading)
    return pack("<HhHLBHHB",
        0xccc1,
        round(reading.temperature * 100),  # temperature (centidegrees C)
        round(reading.humidity * 100),     # rel. humidity (centi-percent)
        round(reading.pressure * 1000),    # pressure (deci-Pascal)
        reading.iaq_accuracy if bsec else 0xff,  # enum
        reading.iaq          if bsec else 0,     # rating 0 (best) to 500 (worst)
        round(reading.eco2)  if bsec else 0,     # ppm
        bat_p,  # battery percentage
    )


def adv_data(reading, bat_p):
    data = \
        adv_service_data(0x181a, sensor_data(reading, bat_p)) + \
        adv_element(0x09, DEVICE_NAME)
    if len(data) > 31:
        raise ValueError("adv_data is {} bytes long, maximum is 31".format(len(data)))
    return data


def loop():
    ble = BLE()
    ble.active(True)
    with display.open() as disp:
        disp.backlight(0)
        with bme680.Bme680() as bme:
            while True:
                # Get battery percentage (4.2V LiPo design maximum to 3.4V cutoff).
                bat_p = round(max(0, min(100, (power.read_battery_voltage() - 3.4) / 0.8 * 100)))

                # Light the blue rocket for as long as the sensor reading takes,
                # but at least for LED_FLASH_DURATION ms.
                end = time.ticks_ms() + LED_FLASH_DURATION
                leds.set_rocket(0, 1)
                reading = bme.get_data()
                time.sleep_ms(min(LED_FLASH_DURATION, end - time.ticks_ms()))
                leds.set_rocket(0, 0)

                # Enable (or update) the BLE advertisement we send.
                ble.gap_advertise(
                    INTERVAL * 1_000_000 + 500_000,
                    adv_data(reading, bat_p),
                    connectable=False,
                )

                time.sleep(1)
                if bat_p <= BATTERY_WARN:
                    red_count = min(11, round(11 / 20 * bat_p))
                    leds.set_all(
                        [(15,0,0)]*(11-red_count) + \
                        [(255,0,0)]*red_count + \
                        [(31,0,0)]*4)
                time.sleep(1)
                leds.set_all(
                    [(15 if bat_p <= BATTERY_WARN else 0,0,0)]*11 + \
                    [(31 if bat_p <= BATTERY_WARN else 0,0,0)]*4)
                time.sleep(INTERVAL - 2)


if __name__ == "__main__":
    loop()
