from machine import I2S, I2C, Pin, SPI
from ssd1306 import SSD1306_I2C
import array
import math
import time
import sdcard
import os
import struct
import rp2

time.sleep(3)

DISPLAY_SD_PIN = Pin(0)
DISPLAY_SCK_PIN = Pin(1)

SCK_PIN = Pin(3)
WS_PIN = Pin(4)
SD_PIN = Pin(2)
I2S_ID = 1

i2cOled=I2C(0, sda=DISPLAY_SD_PIN, scl=DISPLAY_SCK_PIN, freq=400_000)

display = SSD1306_I2C(128,32,i2cOled)

# Assign chip select (CS) pin (and start it high)
cs = machine.Pin(13, machine.Pin.OUT)


spi = SPI(1,baudrate=40000000,sck=Pin(14),mosi=Pin(11),miso=Pin(12))

# Initialize SD card
try:
    sd = sdcard.SDCard(spi, cs)
except:
    display.text("Failed sd card", 0, 0, 1)
    display.text("Insert & restart", 0, 12, 1)
    display.show()

# button = Pin(23, Pin.IN, Pin.PULL_DOWN)

vfs = os.VfsFat(sd)
os.mount(vfs, "/sd")

filename = "/sd/doggie.log"

# Initialize I2S
i2s = I2S(
    I2S_ID,
    sck=SCK_PIN,
    ws=WS_PIN,
    sd=SD_PIN,
    mode=I2S.RX,
    bits=16,
    format=I2S.MONO,
    rate=44100,
    ibuf=20000
)

# Define the size of the input buffer
buffer_size = 1024

# Create an input buffer (array of signed integers)
ibuf = array.array('h', [0]*buffer_size)

y_max = 0

base = 60
tick = 0

def Run(tmr):
    global y_max
    global tick
    
    tick = tick + 1
    
    # Read data from microphone into the buffer
    num_read = i2s.readinto(ibuf)
    
    # Convert buffer to sound pressure levels
    # Simple RMS calculation (you may need a more sophisticated approach for accurate results)
    rms = math.sqrt(sum([x**2 for x in ibuf]) / num_read)
    
    # Convert to decibels
    # Reference pressure for decibel calculation - may require calibration
    ref_pressure = 1e-2
    decibels = 20 * math.log10(rms/ref_pressure)

    y = int((int(decibels) - base) / 2);
    if y < 0: y = 0
    height = 32 - y
    
    if y > y_max: y_max = y
    
    d = int(round(decibels, 2))
    file = open(filename,"ab")
    file.write(struct.pack('>B', y))
    file.close()
    
    display.scroll(-1, 0) # scroll to the left
    display.vline(120, height, y, 1)
    display.fill_rect(0, 0, 120, 12, 0)
    
    if tick < 60:
        runtime = "{}s".format(tick)
    else:
        runtime = "{}m{:02}s".format(math.floor(tick/60), tick % 60)
        
    display.text("{} {}".format(runtime, round(decibels, 2)), 0,0, 1)
    display.show()

timer = machine.Timer()
# timer.init(period=1000, callback=Run) # run Run every 1 second

running = False

def toggleRun():
    global running
    global file
    running = not running
    if running:
        # create a start marker        
        file = open(filename,"ab") # append binary
        file.write(struct.pack('>B', 255)) # start byte
        file.close()
        
        # reset the display
        display.fill(0)
        display.text("Starting...", 0,0, 1)
        display.show()
        time.sleep_ms(3000)
        
        # kick off the timer and fire immediately
        timer.init(period=1000, callback=Run)
        Run(timer)
    else:
        # cancel and remove callback
        timer.deinit()
        time.sleep_ms(1000)
        display.fill(0)
        display.text("Stopping...", 0,0, 1) # just show a message
        display.show()
        time.sleep_ms(3000)
        display.fill(0)
        display.text("Waiting to start", 0,0, 1)
        display.show()


display.fill(0)
display.text("Waiting to start", 0,0, 1)
display.show()

while True:
    if rp2.bootsel_button():
        toggleRun()
    time.sleep_ms(100)
    
