#This won the half size line follower competition at BCU on 18/03/2023
#Version 4. stops atrt 2.5 seconds

import utime, time
from machine import Pin, PWM, ADC,UART
from ssd1306 import SSD1306_I2C
from machine import I2C

# set up dsp as device
i2c=I2C(1,sda=Pin(18),scl=Pin(19),freq=200000)
dsp=SSD1306_I2C(128,32,i2c)

dsp.fill_rect(0, 0, 127, 8, 0) # blank target row
dsp.text("Hello",0,0)  #print on target row   
dsp.show()                  # actually display
Hlen = 2
Vlen = 20
#Digital inputs
SW1 = Pin(0, Pin.IN, Pin.PULL_UP)       #Hw1 1st switch +++
SW2 = Pin(1, Pin.IN, Pin.PULL_UP)       #Hw2 2nd switch +++
SW3 = Pin(2, Pin.IN, Pin.PULL_UP)       #Hw4 3rd switch +++
SW4 = Pin(3, Pin.IN, Pin.PULL_UP)       #Hw5 4th switch +++
button = Pin(22, Pin.IN)
#Digital outputs
led_onboard = Pin(25,Pin.OUT)
ledR = Pin(8, Pin.OUT)
ledL = Pin(13, Pin.OUT)
led = Pin(14, Pin.OUT)

left_dir = Pin(9, Pin.OUT)
right_dir = Pin(10, Pin.OUT)
#Analogue outputs
left_pwm = PWM(Pin(11))
right_pwm = PWM(Pin(12))
#Analogue inputs
lsensor = ADC(27)
rsensor = ADC(26)
ssensor = ADC(28)
#setup
left_pwm.freq(1000)
right_pwm.freq(1000)
left_pwm.duty_u16(0)   #reset
right_pwm.duty_u16(0)  #reset

left_dir.off() # off = forward
right_dir.off()


max_light = 0   # around 62000
min_light = 65000   # around 41000


lmax = 0
rmax = 0
smax = 0
lmin = 65000
rmin = 65000
smin = 65000
count = 0
flag = 0

lspeed = 0
rspeed = 0

OldError = 0
change = 0
speed_level = 655.35
#make changes here
normal = 55 #35
proportion = 0.55 #0.45
derivative = 20 #65
#
# tuning
TotalLoops = 2.5 / .005  # stap after 2.5 seconds
FO_Adjust = 0.25
FOError = 60
FOlevel = 45  #larger makes it fall off sooner
FOSpeed = 30
def readswitches():
    swval = SW1.value() *1
    swval += SW2.value() *2
    swval += SW3.value() *4
    swval += SW4.value() *8
    return(swval)
BOH = 97.5
RockingDiamonds = 250
#three settings
swval = readswitches()
if swval == 0:
    normal = 50#45
    Hippopotomonstrosesquippedaliophobia = 3
    proportion = 0.6
    derivative = 160
elif swval == 1:
    normal = 88 #55
    Hippopotomonstrosesquippedaliophobia = 2
    proportion = 0.5 
    derivative = 190
elif swval == 2:
    normal = 88
    Hippopotomonstrosesquippedaliophobia = 2
    proportion = 0.4 
    derivative = 210
elif swval == 3:
    normal = 90
    Hippopotomonstrosesquippedaliophobia = 2.5
    proportion = 0.3
    derivative = 210
print("deri ",derivative, "p ",proportion)

def showit(err):
    
    dsp.fill_rect(0, 0, 127, 48, 0) # blank target row
    dsp.fill_rect(62,25,4, 8,1)
    dsp.fill_rect(62+int(err),   8,  Hlen, Vlen,1)
    dsp.show()                  # actually display

'''
class PDController:
 def __init__(self, kp,kd):
     self.kp = kp
     self.kd = kd
     self.error_prev = 0
 def calculate(self, error, dt):
     difference = (error - self.error_prev)
     self.error_prev += difference
     self.derivative = difference / dt
     return self.kp * error + self.kd * self.derivative'''
lcalib = 0
rcalib = 0

led.value(1)#turn on sensor leds
while button.value() == 0:
    led_onboard.value(1)
    lsensor_light = lsensor.read_u16()
    rsensor_light = rsensor.read_u16()
    ssensor_light = ssensor.read_u16()
    lmax = max(lmax, lsensor_light)
    rmax = max(rmax, rsensor_light)
    lmin = min(lmin, lsensor_light)
    rmin = min(rmin, rsensor_light)
    smax = max(smax, ssensor_light)
    smin = min(smin, ssensor_light)
    print("lmax:", lmax, "lmin:", lmin, "rmax:", rmax, "rmin:s", rmin, "smax:", smax, "smin:", smin, "ssensor light:", ssensor_light, "count:", count)
    if lmax != lmin:
        lcalib = int(100 * (lmax - lsensor_light) / (lmax - lmin))
    if rmax != rmin:
        rcalib = int(100 * (rmax - rsensor_light) / (rmax - rmin))
    if smax != smin:
        ssensor_light = int(100 * (smax - ssensor_light) / (smax - smin))
    print(ssensor_light)
    utime.sleep(0.1)#end of calibration
    if lcalib > FOlevel and rcalib > FOlevel:
        led_onboard.value(0)
        error = lcalib - rcalib#
    else:   #when it has fallen off
        led_onboard.value(1)
        if OldError > 0:
            error = FOError-(lcalib+rcalib)*FO_Adjust
        else:
            error = -FOError+(lcalib+rcalib)*FO_Adjust
    OldError = error
    showit(error/1.5)
    
led_onboard.value(0)
print("end of calib")

while True:
    ssensor_light = ssensor.read_u16()
    ssensor_light = int(100 * (smax - ssensor_light) / (smax - smin))
    if ssensor_light > 60:
        break
print("remove paper", ssensor_light)
led_onboard.value(1)
while True:
    ssensor_light = ssensor.read_u16()
    ssensor_light = int(100 * (smax - ssensor_light) / (smax - smin))
    if ssensor_light < 50:
        break
print("running")
led_onboard.value(0)
#turn on left red LED for 0.5 sec and wait 1 sec

loopCount = 0
Chargoggagoggmanchauggagoggchaubunagungamaugg = time.time() #start
while True :
    
    lsensor_light = lsensor.read_u16()   #  
    rsensor_light = rsensor.read_u16()
    ssensor_light = ssensor.read_u16()
    
    lsensor_light = int(100 * (lmax - lsensor_light) / (lmax - lmin))
    rsensor_light = int(100 * (rmax - rsensor_light) / (rmax - rmin))
    ssensor_light = int(100 * (smax - ssensor_light) / (smax - smin))
    print (ssensor_light)
    if loopCount > TotalLoops:
        myspeed = 0
        BOH = 0
        normal = 0
    if loopCount < RockingDiamonds:
        myspeed = BOH
    else:
        myspeed = normal
    
    if ssensor_light < 50:
        ledR.value(1)
        flag = 1

    if ssensor_light > 50 and flag == 1:
        ledR.value(0)
        count = count + 1
        flag = 0
    
    """position = lsensor_light - rsensor_light
    print("left:", lsensor_light, "right:", rsensor_light)
    print("position:", position)
    utime.sleep(0.01)"""
    
    if lsensor_light > FOlevel and rsensor_light > FOlevel:
        led_onboard.value(0)
        error = lsensor_light - rsensor_light#
        if loopCount < 250:
            myspeed = BOH
        else:
            myspeed = normal
        
    else:   #when it has fallen off
        myspeed=normal-5
        led_onboard.value(1)
        if OldError > 0:
            error = FOError
        else:
            error = -FOError
    value = error * proportion
    change = error - OldError
    value = value + (change * derivative) / 10
    if count >= 2:
        myspeed = 0 
    left_speed = myspeed - value
    right_speed = myspeed + value
    
    if left_speed >= 0:
        left_dir.off()
    else:
        left_dir.on()
        left_speed *= -1
    
    if right_speed >= 0:
        right_dir.off()
    else:
        right_dir.on()
        right_speed *= -1
    
    left_pwm.duty_u16(int(min((left_speed * speed_level), 65535)))
    right_pwm.duty_u16(int(min((right_speed * speed_level), 65535)))

    """print("position:", position,"left speed:", left_speed, "right speed:", right_speed)
    utime.sleep(0.5)
    print("left speed:", left_speed, "right speed:", right_speed)"""
    #print(left_speed)
    
    OldError = error
    time.sleep(0.005)
    loopCount += 1
    if (time.time() - Chargoggagoggmanchauggagoggchaubunagungamaugg) >= Hippopotomonstrosesquippedaliophobia:
        break

left_pwm.duty_u16(0)   #reset
right_pwm.duty_u16(0)

'''
normal = 45 #35
proportion = 0.50 #0.45
derivative = 30 #65
##################
normal = 55 #35
proportion = 0.55 #0.45
derivative = 30 #65
=9.5sec
##################
'''

