def __init__(self, pin_pwm_l, pin_in1_l, pin_in2_l, pin_encoder_l, pin_pwm_r, pin_in1_r, pin_in2_r, pin_encoder_r,
              freq=1000, uart_id=0, motorCruisingSpeed=30, motorCruisingDistance=120):
     # setup hbridge motor controller pin assignments
     self.motorLeft = HBridge(
         pin_pwm_l,  pin_in1_l, pin_in2_l, freq)
     self.motorRight = HBridge(
         pin_pwm_r,  pin_in1_r, pin_in2_r, freq)
     self.motorEncoderL = Pin(pin_encoder_l, Pin.IN)
     self.motorEncoderR = Pin(pin_encoder_r, Pin.IN)
     # PI pico - not tested with esp32 (UART implementations seem to vary)
     self.uart = UART(uart_id, 9600)  # uart0 uses pins 1 (tx) and 2 (rx)
     self.motorCruisingSpeed = motorCruisingSpeed
     self.motorCruisingDistance = motorCruisingDistance
     gc.collect()
     gc.disable()
class Base_Motor_Controller():

    debug = False

    uartData = ""

    # Set up motor encoder interface
    motorMode = "L"  # R=Run T=Turn L=Listen
    baseTargetDistance = 0  # In average encoder ticks
    motorEncoderCntR = 0
    motorEncoderCntTotalR = 0
    motorEncoderCntL = 0
    motorEncoderCntTotalL = 0

    # Current speed setting
    speed = 0
    # motorSpeedMin is the minimum a speed can be set to or adjusted
    motorSpeedMin = 10
    motorCruisingSpeed = 30
    motorCruisingDistance = 120
    speedDirection = 1

    # dFactor represents the coefficient for the amount that overall distance
    # corrections should be applied on one monitoring cycle. Values less than 1
    # will insure distance corrections are done slowly over multiple monitoring
    # cycles
    dFactor = 0.2

    def __init__(self, pin_pwm_l, pin_in1_l, pin_in2_l, pin_encoder_l, pin_pwm_r, pin_in1_r, pin_in2_r, pin_encoder_r,
                 freq=1000, uart_id=0, motorCruisingSpeed=30, motorCruisingDistance=120):
        # setup hbridge motor controller pin assignments
        self.motorLeft = HBridge(
            pin_pwm_l,  pin_in1_l, pin_in2_l, freq)
        self.motorRight = HBridge(
            pin_pwm_r,  pin_in1_r, pin_in2_r, freq)
        self.motorEncoderL = Pin(pin_encoder_l, Pin.IN)
        self.motorEncoderR = Pin(pin_encoder_r, Pin.IN)
        # PI pico - not tested with esp32 (UART implementations seem to vary)
        self.uart = UART(uart_id, 9600)  # uart0 uses pins 1 (tx) and 2 (rx)
        self.motorCruisingSpeed = motorCruisingSpeed
        self.motorCruisingDistance = motorCruisingDistance
        gc.collect()
        gc.disable()

    def run(self, speed):
        self._setSpeed(speed)
        self.motorEncoderCntR = 0
        self.motorEncoderCntL = 0
        self.motorEncoderR.irq(trigger=Pin.IRQ_FALLING,
                               handler=self._motorEncoderCallbackR)
        self.motorEncoderL.irq(trigger=Pin.IRQ_FALLING,
                               handler=self._motorEncoderCallbackL)

        self.statusTimer = Timer()
        self.motorMode = "R"
        self.statusTimer.init(freq=10, mode=Timer.PERIODIC,
                              callback=self._monitorStatus)
        if (self.speed > 0):
            self.motorLeft.forward(abs(self.speed))
            self.motorRight.forward(abs(self.speed))
        if (self.speed < 0):
            self.motorLeft.reverse(abs(self.speed))
            self.motorRight.reverse(abs(self.speed))
        if (self.speed == 0):
            self.stop()

    def runDistance(self, speed, distance):
        self.baseTargetDistance = distance
        self.run(speed)

    def turn(self, speed):
        # rotate the body at the velocity defined by speed
        self._setSpeed(speed)
        self.motorEncoderCntR = 0
        self.motorEncoderCntL = 0
        self.motorEncoderR.irq(trigger=Pin.IRQ_FALLING,
                               handler=self._motorEncoderCallbackR)
        self.motorEncoderL.irq(trigger=Pin.IRQ_FALLING,
                               handler=self._motorEncoderCallbackL)

        self.statusTimer = Timer()
        self.motorMode = "T"
        self.statusTimer.init(freq=10, mode=Timer.PERIODIC,
                              callback=self._monitorStatus)
        if (self.speed > 0):
            self.motorLeft.reverse(abs(self.speed))
            self.motorRight.forward(abs(self.speed))
        if (self.speed < 0):
            self.motorLeft.forward(abs(self.speed))
            self.motorRight.reverse(abs(self.speed))
        if (self.speed == 0):
            self.stop()

    def turnDistance(self, speed, distance):
        self.baseTargetDistance = distance
        self.turn(speed)

    def stop(self):
        # Use stop when the motor control should be re-initialized
        # otherwise the next run() will continue to compensate for
        # existing distance discrepancies
        self.motorLeft.stop()
        self.motorRight.stop()
        self.baseTargetDistance = 0
        self.motorEncoderR.irq(handler=None)
        self.motorEncoderL.irq(handler=None)
        #  Report back if a movement happened
        if self.motorMode in ["R", "T"]:
            # Send back info about average base movement in the form
            # of an R value (after a run) or a T value
            totalMovement = round(
                (self.motorEncoderCntTotalL + self.motorEncoderCntTotalR)/2)
            if (self.speed < 0):
                totalMovement *= -1
            movementString = self.motorMode + "%+06d" % totalMovement + "\n"
            self.uart.write(movementString.encode())
        #  Clean up movement info.
        self.speed = 0
        self.motorEncoderCntTotalR = 0
        self.motorEncoderCntTotalL = 0
        self.motorMode = "L"
        gc.collect()

    def start(self, debug=False):
        self.motorMode = "L"
        self.debug = debug
        self.statusTimer = Timer()
        self.statusTimer.init(freq=10, mode=Timer.PERIODIC,
                              callback=self._monitorStatus)

    def quit(self):
        try:
            self.statusTimer.deinit()
            print("Quit OK")
        except:
            print("Could not deinit status timer!")

    def reboot(self):
        # Hardware reboot to get everything restarted
        machine.reset()

    def _setSpeed(self, speed):
        self.speed = speed
        if speed < 0:
            self.speedDirection = 1
        else:
            self.speedDirection = -1
        if abs(self.speed) < self.motorSpeedMin:
            self.speed = self.motorSpeedMin * self.speedDirection

    def _monitorStatus(self, timer):
        if self.motorMode != "L":
            self._adjustLRSpeed(self.motorMode == "R")
            self.motorEncoderCntR = 0
            self.motorEncoderCntL = 0
        self._uartListen()
        gc.collect()

    def _adjustLRSpeed(self, isRun):
        # Checks the the distance moved on each Tick and
        # reduce the faster motor speed if needed

        # Slow down to cruising speed if within cruising distance
        if self.baseTargetDistance > 0:
            baseDistance = ((self.motorEncoderCntTotalR +
                             self.motorEncoderCntTotalL)/2)
            if self.speed != 0 and (self.baseTargetDistance - baseDistance < self.motorCruisingDistance) and self.motorCruisingSpeed < self.speed:
                self.speed = self.motorCruisingSpeed
                if self.debug:
                    print("Crusing!")

        speedL = self.speed
        speedR = self.speed
        if (self.motorEncoderCntR > 0 and self.motorEncoderCntL > 0 and self.motorEncoderCntTotalL > 0 and self.motorEncoderCntTotalR > 0):
            speedDiff = abs((self.motorEncoderCntL -
                             self.motorEncoderCntR) / ((self.motorEncoderCntR + self.motorEncoderCntL)/2))
            #  Distance difference is based on overall difference in distance divided by average delta distance
            distDiff = abs((self.motorEncoderCntTotalL -
                            self.motorEncoderCntTotalR) / ((self.motorEncoderCntR + self.motorEncoderCntL)/2))

            #  Set the difference to reduce faster motor speed based on if positive or negative speed
            if (self.motorEncoderCntL < self.motorEncoderCntR):
                speedR -= round(speedR * speedDiff)
            else:
                speedL -= round(speedL * speedDiff)

            # If speeds must be adjusted to compensate for over all distance
            # then do it gradually based on dFactor
            if (self.motorEncoderCntTotalL < self.motorEncoderCntTotalR):
                speedR -= round(speedR * distDiff * self.dFactor)
            else:
                speedL -= round(speedL * distDiff * self.dFactor)
            if self.debug:
                print("speedDiff:" + str(speedDiff) + " distDiff:" + str(distDiff) + " deltaL:" +
                      str(self.motorEncoderCntL) + " deltaR:" + str(self.motorEncoderCntR))

        # If we are moving a certain distance then check the distance the base has moved (average of the two motor encoders)
        # vs the  baseTargetDistance
        if self.baseTargetDistance > 0:
            baseDistance = ((self.motorEncoderCntTotalR +
                             self.motorEncoderCntTotalL)/2)
            baseSpeed = ((self.motorEncoderCntR + self.motorEncoderCntL)/2)
            # slowdown when distance will be exceeded at current speed
            if self.speed != 0 and (baseSpeed + baseDistance > self.baseTargetDistance) and baseSpeed > 0:
                slowing = (self.baseTargetDistance -
                           baseDistance) / baseSpeed
                speedL *= slowing
                speedR *= slowing
                if self.debug:
                    print("Slowing down speedL:" +
                          str(speedL) + "  speedR:" + str(speedR))
            # Stop if distance exceeded
            if baseDistance > self.baseTargetDistance:
                if self.debug:
                    print("Distance exceeded, stopped  baseDist:" + str(baseDistance) + " distL:" +
                          str(self.motorEncoderCntTotalL) + " distR:" + str(self.motorEncoderCntTotalR))
                self.stop()
        if self.speed != 0:
            # Set motor directions depending on if it is a RUN or Turn action
            # and if direction is positive or negative
            if abs(speedL) < self.motorSpeedMin:
                speedL = self.motorSpeedMin
            if abs(speedR) < self.motorSpeedMin:
                speedR = self.motorSpeedMin
            if self.debug:
                print("speedL:" + str(speedL) + " speedR:" + str(speedR) + " distL:" +
                      str(self.motorEncoderCntTotalL) + " distR:" + str(self.motorEncoderCntTotalR))
                print("")
            if (isRun):
                if (self.speedDirection == 1):
                    self.motorLeft.forward(abs(speedL))
                    self.motorRight.forward(abs(speedR))
                else:
                    self.motorLeft.reverse(abs(speedL))
                    self.motorRight.reverse(abs(speedR))
            else:
                if (self.speedDirection == -1):
                    self.motorLeft.reverse(abs(speedL))
                    self.motorRight.forward(abs(speedR))
                else:
                    self.motorLeft.forward(abs(speedL))
                    self.motorRight.reverse(abs(speedR))

    def _motorEncoderCallbackR(self, pin):
        self.motorEncoderCntR += 1
        self.motorEncoderCntTotalR += 1

    def _motorEncoderCallbackL(self, pin):
        self.motorEncoderCntL += 1
        self.motorEncoderCntTotalL += 1

    def _uartListen(self):
        while (self.uart.any()):
            uartIn = self.uart.read(1).decode("utf-8")
            if uartIn == "\n":
                self._processData()
                self.uartData = ""
            else:
                self.uartData += uartIn

    def _processData(self):
        # print(str(self.uart.any()))
        command = ""
        velocity = 0
        distance = 0
        # print("_processData:" + self.uartData + " " + str(len(self.uartData)))
        if len(self.uartData) == 2:
            command = self.uartData
            if command not in ["SP", "RB", "QT"]:
                command = ""
        if len(self.uartData) == 6:
            command = self.uartData[0:2]
            if command not in ["RV", "TV"]:
                command = ""
            else:
                try:
                    velocity = int(self.uartData[2:6])
                except:
                    command = ""
                    velocity = 0
        if len(self.uartData) == 11:
            command = self.uartData[0:2]
            if command not in ["RD", "TD"]:
                command = ""
            else:
                try:
                    velocity = int(self.uartData[2:6])
                    distance = int(self.uartData[6:11])
                except:
                    command = ""
                    velocity = 0
                    distance = 0
        if len(command) > 0:
            if self.debug:
                print("commandMsg:", self.uartData + " command:" + command +
                      " velocity:" + str(velocity) + " distance:" + str(distance))
            if command == "QT":
                self.stop()
                self.quit()
            elif command == "SP":
                self.stop()
            elif command == "RB":
                self.reboot()
            elif command == "RV":
                self.run(velocity)
            elif command == "TV":
                self.turn(velocity)
            elif command == "RD":
                self.runDistance(velocity, distance)
            elif command == "TD":
                self.turnDistance(velocity, distance)