# SPDX-FileCopyrightText: 2018 Mikey Sklar for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
This Code uses the:
* Adafruit LCD backpack using MCP23008 I2C expander
* Maxbotic LV-EZ1 Ultrasonic Sensor

Tested with the Trinket M0
The ultrasonic sensor and pin use should be Gemma M0 compatible
This sketch reads the LV-EZ1 by pulse count
Then prints the distance to the LCD and python console

The circuit:
* 5V to Trinket M0 USB or BAT pin, I2C Backpack 5V and EZ1 +5
* GND to Trinket M0 GND pin, I2C Backpack GND and EZ1 GND
* Display I2C Backpack SLK to Trinket GPIO #2
* Display I2C backpack SDA to Trinket GPIO #0
* LV-EZ1 Ultrasonic Sensor PW pin to Trinket GPIO #1
* Backlight can be hard wired by connecting LCD pin 16, 17 or 18 to GND
"""

import time

import adafruit_character_lcd
import board
import busio
import pulseio

ez1pin = board.D1  # Trinket GPIO #1

# i2c LCD initialize bus and class
i2c = busio.I2C(board.SCL, board.SDA)
cols = 16
rows = 2
lcd = adafruit_character_lcd.Character_LCD_I2C(i2c, cols, rows)

# calculated mode or median distance
mode_result = 0

# pulseio can store multiple pulses
# read in time for pin to transition
samples = 18
pulses = pulseio.PulseIn(board.D1, maxlen=samples)

# sensor reads which are in range will be stored here
rangevalue = [0, 0, 0, 0, 0, 0, 0, 0, 0]

# 25ms sensor power up pause
time.sleep(.25)


def tof_cm(time_of_flight):
    """
    EZ1 ultrasonic sensor is measuring "time of flight"
    Converts time of flight into distance in centimeters
    """
    convert_to_cm = 58
    cm = time_of_flight / convert_to_cm

    return cm


def tof_inches(time_of_flight):
    """
    EZ1 ultrasonic sensor is measuring "time of flight"
    Converts time of flight into distance in inches
    """
    convert_to_inches = 147
    inches = time_of_flight / convert_to_inches

    return inches


def METHOD_NAME(x):
    """
    find the mode (most common value reported)
    will return median (center of sorted list)
    should mode not be found
    """
    n = len(x)

    if n == 0:  # If somehow there is a null array
        return 0

    if n == 1:       # In trivial case of a one element array
        return x[0]  # just return the element as the mode.

    max_count = 0
    mode = 0
    bimodal = 0
    counter = 0
    index = 0

    while index < (n - 1):
        prev_count = counter
        counter = 0

        while (x[index]) == (x[index + 1]):
            counter += 1
            index += 1

        if (counter > prev_count) and (counter > max_count):
            mode = x[index]
            max_count = counter
            bimodal = 0

        if counter == 0:
            index += 1

        # If the dataset has 2 or more modes.
        if counter == max_count:
            bimodal = 1

        # Return the median if there is no mode.
        if (mode == 0) or (bimodal == 1):
            mode = x[int(n / 2)]

        return mode


while True:

    # wait between samples
    time.sleep(.5)

    if len(pulses) == samples:
        j = 0  # rangevalue array counter

        # only save the values within range
        # range readings take 49mS
        # pulse width is .88mS to 37.5mS
        for i in range(0, samples):
            tof = pulses[i]  # time of flight - PWM HIGH

            if 880 < tof < 37500:
                if j < len(rangevalue):
                    rangevalue[j] = tof_cm(tof)
                    j += 1

        # clear pulse samples
        pulses.clear()  # clear all values in pulses[]

        # sort samples
        rangevalue = sorted(rangevalue)

        # returns mode or median
        mode_result = int(METHOD_NAME(rangevalue))

        # python console prints both centimeter and inches distance
        cm2in = .393701
        mode_result_in = mode_result * cm2in
        print(mode_result, "cm", "\t\t", int(mode_result_in), "in")

        # result must be in char/string format for LCD printing
        digit_string = str(mode_result)

        lcd.clear()
        lcd.message("Range: ")  # write to LCD
        lcd.message("    ")
        lcd.message(digit_string)
        lcd.message("cm")

        time.sleep(2)