import board
import neopixel
import time
import math
import array
import digitalio
import busio
import analogio
import simpleio
import adafruit_thermistor
import adafruit_lis3dh
import microcontroller
import audiobusio
from rainbowio import colorwheel

from adafruit_ble import BLERadio
from adafruit_ble import advertising
#from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.uuid import StandardUUID
from adafruit_ble.services import Service
import adafruit_ble.advertising.standard
from adafruit_ble.characteristics.int import Uint8Characteristic, Int16Characteristic

DEVICE_NAME = "CP Bluefruit - Living Room"
INTERVAL = 0.25
SENSITIVITY = 0.01

a = advertising.Advertisement()
a.complete_name = "CP Bluefruit"
a.flags.general_discovery = 1
a.flags.le_only = 1

print('[' + advertising.to_hex(a.__bytes__()) + ']')
uuid = StandardUUID(0xD2FC) # The UUID of BTHome Data, as given by Allterco Robotics.
print('UUID: ' + uuid.__str__())

thermistor = adafruit_thermistor.Thermistor(board.TEMPERATURE, 10000, 10000, 23.65, 3950)

# The Circuit Playground uses the Everlight ALS-PT19 as the sensor.
# The datasheet shows a graph of lux vs voltage and the lux ranges from 0 to 10000
# The output in Python ranges from 0 to 65535. 
# So I'll map the range 0 65535 to 0 to 10000 as a rough estimate
light = analogio.AnalogIn(board.LIGHT)

mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       sample_rate=16000, bit_depth=16)

buttonA = digitalio.DigitalInOut(board.BUTTON_A)
buttonA.switch_to_input(pull=digitalio.Pull.DOWN)
buttonB = digitalio.DigitalInOut(board.BUTTON_B)
buttonB.switch_to_input(pull=digitalio.Pull.DOWN)

i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT)
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19, int1=int1)
lis3dh.range = adafruit_lis3dh.RANGE_8_G

class BTHomeService(Service):
    # UUID given by Alterco Robotics
    uuid = StandardUUID(0xFCD2)
    # NOTE: There are no characteristics for this service.
    # That is, no explicitely defined BLE characteristics will be used. Instead we just have whatever BTHome 
    # has defined internally in their list. But these are not official characteristics.

    # bit 0: Encryption flag - 0 non-encrypted, 1 encrypted
    # bit 1: reserved
    # bit 2: Trigger based device flag - 0 regular interval, 1 non-regular interval
    # bit 3-4: Reserved
    # bit 5-7: BTHome Version  - version 2, so 010
    # This gives 01000000b = 0x40
    #device_information = Uint8Characteristic(min_value=0x40,max_value=0x40, initial_value=0x40)
    #temperatureField = 0x45 # Data type for 0.1 precision temperature field in BTHome
    #temperatureValue = 0x0000  # Dummy info for now
    
    
    def __get__(self, obj, objtype=None):
        super.__get__(obj, objtype)
    
class BTHomeAd(advertising.Advertisement):
    btService = BTHomeService()
    s = advertising.standard.ServiceData(btService)
    
class BTHomeTemperature:
    global thermistor
    objectID = 0x45     # temperature
    
    def __get__(self, obj, objtype=None):
        b = bytearray(3)
        b[0] = self.objectID
        temperature = int(thermistor.temperature * 10)
        b[1] =  temperature & 0xFF
        b[2] = (temperature & 0xFF00) >> 8
        return b

class BTHomeLight:
    objectID = 0x05     # illuminance
    
    def __get__(self, obj, objtype=None):
        b = bytearray(4)
        b[0] = self.objectID
        lux = int(simpleio.map_range(light.value, 0, 65535, 0, 10000) * 100)
        b[3] = (lux & 0xFF0000) >> 16
        b[2] = (lux & 0x00FF00) >> 8
        b[1] = lux & 0x00FF
        return b

class BTHomeSound:
    objectID = 0x2A     # sound - boolean
    # Number of samples to read at once.
    NUM_SAMPLES = 160
    
    
    def __init__(self):
        # Record an initial sample to calibrate. Assume it's quiet when we start.
        self.samples = array.array('H', [0] * self.NUM_SAMPLES)
        mic.record(self.samples, len(self.samples))
    
    def mean(self, values):
        return sum(values) / len(values)
    
    # Remove DC bias before computing RMS.
    def normalized_rms(self, values):
        minbuf = int(self.mean(values))
        samples_sum = sum(
            float(sample - minbuf) * (sample - minbuf)
            for sample in values
        )
        return math.sqrt(samples_sum / len(values))
        
    def get(self):
        mic.record(self.samples, len(self.samples))
        magnitude = self.normalized_rms(self.samples)
        return(magnitude)
        # You might want to print this to see the values.
        # print(magnitude)

class BTHomeButton:
    objectID = 0x3A     # button
    
    def __init__(self, button):
        self.button = button
        self.current_value = button.value
    
    def __get__(self, obj, objtype=None):
        b = bytearray(2)
        b[0] = self.objectID
        if self.button.value != self.current_value:
            if not self.current_value:
                # Button pressed
                self.current_value = True
                b[1] = 0x01
            else:
                # Button released
                self.current_value = False
                b[1] = 0x00
        else:
            b[1] =0x00
        return b
    
class BTHomeAccelerometer:
    objectID = 0x51
    
    def __get__(self, obj, objtype=None):
         b = bytearray(3)
         b[0] = self.objectID
         x,y,z = lis3dh.acceleration
         
         '''
         x = int(abs(x) * 1000)
         b[1] = x & 0x0FF
         b[2] = (x & 0x00FF00) >> 8

         b[3] = self.objectID
         y = int(y * 1000)
         b[4] = y & 0x0FF
         b[5] = (y & 0x00FF00) >> 8
         '''
         b[0] = self.objectID
         z = int(z * 1000)
         b[1] = z & 0x0FF
         b[2] = (z & 0x00FF00) >> 8
         
         return b
         
class BTHomeCircuitPlayground:
    device_info = bytearray(1)
    device_info[0] = 0x40
    temp = BTHomeTemperature()
    lux = BTHomeLight()
    A = BTHomeButton(buttonA)
    B = BTHomeButton(buttonB)
    z_accel = BTHomeAccelerometer()
    
    def get(self):
        # In objectID order    0x05     0x3A        0x45           0x51
        return self.device_info + self.lux + self.A + self.temp + self.z_accel
    
bthomeCP = BTHomeCircuitPlayground()
btad = BTHomeAd()
btad.complete_name = "CP BTHome"
btad.flags.general_discovery = True
btad.flags.le_only = True
btad.s = bthomeCP.get()
print('[' + advertising.to_hex(btad.__bytes__()) + ']')

ble = BLERadio()
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.2, auto_write=False)

while True:
    btad.s = bthomeCP.get()
    #print('[' + advertising.to_hex(btad.__bytes__()) + ']')
    
    ble.start_advertising(btad)
    # Change this to a non-blocking time comparison
    # That way we can check for button presses in the meantime.
    time.sleep(INTERVAL)
    ble.stop_advertising()