TypeError: can't convert __enter__ to float

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
User avatar
ardthusiast
Posts: 25
Joined: Tue Feb 08, 2022 4:13 pm

TypeError: can't convert __enter__ to float

Post by ardthusiast » Fri Mar 04, 2022 4:46 pm

Hello,

I am trying to use the _thread module to constantly fetch data from a GY271 compass module while moving a robot, but I get this error message:

Code: Select all

Unhandled exception in thread started by -37.72
Traceback (most recent call last):
  File "robot.py", line 46, in __update_info
  File "gy271.py", line 77, in direction
TypeError: can't convert __enter__ to float
Line 77 in my compass module code is this:

Code: Select all

direction_radians = math.atan2(y, x)
However, it works fine when I don't run it in a thread. Any ideas how I can fix this?

My code for reference:

Code: Select all

import math
import uasyncio
import json

class GY271:
    """Class to control the GY271 compass module."""

    scales = {
        0.88: [0, 0.73],
        1.3: [1, 0.92],
        1.9: [2, 1.22],
        2.5: [3, 1.52],
        4.0: [4, 2.27],
        4.7: [5, 2.56],
        5.6: [6, 3.03],
        8.1: [7, 4.35]
    }

    def __init__(self, i2c_object, declination = (0, 0), radians = False, calibration_file = 'calibration.json', gauss = 1.3):
        """Initialize the module."""
        # i2c address definitions
        self.address = 0x1E
        self.config_address_a = 0x00
        self.config_address_b = 0x01
        self.mode_address = 0x02
        self.data_address = 0x03
        self.status_address = 0x09

        self.declination_degrees, self.declination_minutes = declination
        self.declination = (self.declination_degrees + self.declination_minutes / 60) * math.pi / 180

        self.scale_register_value, self.scale = self.scales[gauss]

        self.radians = radians

        self.i2c_object = i2c_object

        self.calibration_file = calibration_file
        with open(self.calibration_file) as f:
            self.calibration_values = json.load(f)

        if self.address not in self.i2c_object.scan():
            raise RuntimeError("Failed to get response from device.")

        self.i2c_object.writeto_mem(self.address, self.config_address_a, bytearray([0x70]))    # 8 Average, 15 Hz, normal measurement
        self.i2c_object.writeto_mem(self.address, self.config_address_b, bytearray([self.scale_register_value << 5]))    # Scale
        self.i2c_object.writeto_mem(self.address, self.mode_address, bytearray([0x00]))    # Continuous measurement

    @property
    def raw_values(self):
        """Get x, y, and z values from the module. Returns the ordered triple (x, y, z)."""
        data = bytearray(6)
        self.i2c_object.readfrom_mem_into(self.address, self.data_address, data)
        x = self.__convert_values(data, 0) + self.calibration_values['xb']
        y = self.__convert_values(data, 4) + self.calibration_values['yb']
        z = self.__convert_values(data, 2)

        return (x, y, z)

    def __convert_values(self, data, offset):
        """Convert data values from bytes to integers. Returns the value rounded to 4 decimals."""
        value = self.__twos_complement(data[offset] << 8 | data[offset + 1], 16)
        if value == -4096:
            return None
        return round(value * self.scale, 4)

    def __twos_complement(self, value, length):
        """Convert twos complement to integer value. Returns the integer value."""
        if (value & (1 << (length - 1))):
            value = value - (1 << length)
        return value

    @property
    def direction(self):
        """Get the current direction. Returns direction in degrees, unless 'radians = True' is specified."""
        (x, y, z) = self.raw_values
        direction_radians = math.atan2(y, x)
        direction_radians += self.declination

        if direction_radians < 0:
            direction_radians += 2 * math.pi

        elif direction_radians > 2 * math.pi:
            direction_radians += 2 * math.pi

        direction_degrees = math.degrees(direction_radians)

        if self.radians:
            return direction_radians

        return direction_degrees

    def calibrate(self, mode, verbose = False):
        """Calibrate the sensor."""
        if mode == "start":
            self.x_min = 0
            self.x_max = 0
            self.y_min = 0
            self.y_max = 0

            self.calibration_values = {"xb": 0, "yb": 0}

            self.cal_done = False

            async def cal_routine():
                while not self.cal_done:
                    x, y, z = self.raw_values
                    self.x_min = min(x, self.x_min)
                    self.x_max = max(x, self.x_max)
                    self.y_min = min(y, self.y_min)
                    self.y_max = max(y, self.y_max)

                    if verbose:
                        print("x: " + str(y))
                        print("y: " + str(y))
                        print("xmin: " + str(self.x_min))
                        print("ymin: " + str(self.y_min))
                        print("xmax: " + str(self.x_max))
                        print("ymax: " + str(self.y_max))

                    await uasyncio.sleep_ms(67)

            uasyncio.run(cal_routine())

        elif mode == "finish":
            self.cal_done = True
            xb = 0.5 * (self.x_max - self.x_min) - self.x_max
            yb = 0.5 * (self.y_max - self.y_min) - self.y_max
            calibration = {"xb": xb, "yb": yb}

            if verbose:
                print("xoffset: " + str(xb))
                print("yoffset: " + str(yb))
                print("xmin: " + str(self.x_min))
                print("ymin: " + str(self.y_min))
                print("xmax: " + str(self.x_max))
                print("ymax: " + str(self.y_max))
                print("calibration dictionary: " + str(calibration))

            with open(self.calibration_file, 'w') as f:
                json.dump(calibration, f)

        elif mode == "abort":
            self.cal_done = True

        else:
            raise RuntimeError("Invalid calibration mode. Valid modes are 'start', 'finish', and 'abort'.")

    def angle_sum(self, angle_1, angle_2):
        if self.radians:
            sum_angle = (angle_1 + angle_2) % 2 * math.pi
            return sum_angle
        else:
            sum_angle = (angle_1 + angle_2) % 360
            return sum_angle
And the code that fetches the values within a thread:

Code: Select all

import math
import _thread as thread

class Searching:
    """A class to manage the robot's movement, location tracking, and DFS systems."""

    def __init__(self, motor_left, motor_right, compass, wheel_diameter, default_speed = 130, offset_left = 0, offset_right = 0):
        """Initialize the system."""
        self.motor_left = motor_left
        self.motor_right = motor_right

        self.compass = compass
        compass.radians = True
        self.direction = self.compass.direction

        self.offset_left = offset_left
        self.offset_right = offset_right
        self.default_speed = default_speed

        self.step_size = wheel_diameter * math.pi / 80
        self.locations = [(0, 0)]
        self.waypoints = [(0, 0)]

        self.enable_location = True
        self.rotating = False
        self.lock = thread.allocate_lock()
        thread.start_new_thread(self.__update_info, ())

    def forward(self, steps, speed = None):
        """Move the robot forward for a given number of steps at a given speed."""
        if self.offset_left > self.offset_right:
            self.motor_right.step(steps, frequency = (speed or self.default_speed) + self.offset_right)
            self.motor_left.step_infinite(1, frequency = (speed or self.default_speed) + self.offset_left)
            self.motor_right.finished_stepping += self.motor_left.stop
        elif self.offset_right > self.offset_left:
            self.motor_left.step(steps, frequency = (speed or self.default_speed) + self.offset_right)
            self.motor_right.step_infinite(1, frequency = (speed or self.default_speed) + self.offset_left)
            self.motor_left.finished_stepping += self.motor_right.stop()
        elif self.offset_left == self.offset_right:
            self.motor_left.step(steps, frequency = (speed or self.default_speed) + self.offset_right)
            self.motor_right.step(steps, frequency = (speed or self.default_speed) + self.offset_right)

    def __update_info(self):
        """Constantly update information."""
        while True:
            self.direction = self.compass.direction
            if self.offset_left > self.offset_right:
                if self.motor_right.steps_since_reset >= 80 and self.enable_location:
                    distance = self.motor_right.steps_since_reset * self.step_size
                    location = self.locations[-1]
                    direction = self.compass.direction
                    x = location[0] + distance * math.sin(direction)
                    y = location[1] + distance * math.cos(direction)
                    self.locations.append((x, y))
                    self.motor_right.steps_since_reset = 0
                    self.motor_left.steps_since_reset = 0

            elif self.offset_right > self.offset_left:
                if self.motor_left.steps_since_reset >= 80 and self.enable_location:
                    distance = self.motor_left.steps_since_reset * self.step_size
                    location = self.locations[-1]
                    direction = self.compass.direction
                    x = location[0] + distance * math.sin(direction)
                    y = location[1] + distance * math.cos(direction)
                    self.locations.append((x, y))
                    self.motor_right.steps_since_reset = 0
                    self.motor_left.steps_since_reset = 0

            elif self.offset_left == self.offset_right:
                if self.motor_right.steps_since_reset >= 80 and self.motor_left.steps_since_reset >= 80 and self.enable_location:
                    distance = self.motor_right.steps_since_reset * self.step_size
                    location = self.locations[-1]
                    direction = self.compass.direction
                    x = location[0] + distance * math.sin(direction)
                    y = location[1] + distance * math.cos(direction)
                    self.locations.append((x, y))
                    self.motor_right.steps_since_reset = 0
                    self.motor_left.steps_since_reset = 0

            if self.rotating:
                self.__angle_test(self.rotation_angle)

    def __angle_test(self, angle):
        """Test if the current angle is the specified angle."""
        if math.isclose(self.compass.angle_sum(self.initial_direction, angle), self.direction, rel_tol = 0.0872665):
            self.rotating = False
            self.__rotate_stop()

    def rotate(self, angle, speed = None):
        """Rotate the robot a certain number of degrees at a certain speed."""
        self.rotation_angle = math.radians(angle)
        rotation_speed = speed or self.default_speed
        self.enable_location = False
        self.rotating = True
        rotation = int(angle / abs(angle))
        self.initial_direction = self.direction
        self.motor_left_steps = self.motor_left.steps_since_reset
        self.motor_right_steps = self.motor_right.steps_since_reset
        self.motor_left.step_infinite(rotation, frequency = rotation_speed + self.offset_left)
        self.motor_right.step_infinite(-1 * rotation, frequency = rotation_speed + self.offset_left)

    def __rotate_stop(self):
        self.motor_left.stop()
        self.motor_right.stop()
        self.motor_left.steps_since_reset = self.motor_left_steps
        self.motor_right.steps_since_reset = self.motor_right_steps
        self.enable_location = True

I am running this on the RP2040 Pro MIcro.

stijn
Posts: 735
Joined: Thu Apr 24, 2014 9:13 am

Re: TypeError: can't convert __enter__ to float

Post by stijn » Fri Mar 04, 2022 7:37 pm

Code: Select all

(x, y, z) = self.raw_values
needs to be

Code: Select all

(x, y, z) = self.raw_values()
?

User avatar
ardthusiast
Posts: 25
Joined: Tue Feb 08, 2022 4:13 pm

Re: TypeError: can't convert __enter__ to float

Post by ardthusiast » Fri Mar 04, 2022 7:48 pm

I don't think so, I preceded the definition of

Code: Select all

raw_values
with the

Code: Select all

@property
decorator. The code for the compass works when it's not running on a thread.

User avatar
scruss
Posts: 360
Joined: Sat Aug 12, 2017 2:27 pm
Location: Toronto, Canada
Contact:

Re: TypeError: can't convert __enter__ to float

Post by scruss » Fri Mar 04, 2022 8:34 pm

_thread can give unexpected results on RP2040. It's probably best avoided until the bug is understood and resolved

User avatar
ardthusiast
Posts: 25
Joined: Tue Feb 08, 2022 4:13 pm

Re: TypeError: can't convert __enter__ to float

Post by ardthusiast » Fri Mar 04, 2022 11:24 pm

Thanks for clearing that up. I will look into alternatives to using _thread.

samneggs
Posts: 20
Joined: Tue Jun 15, 2021 12:57 am

Re: TypeError: can't convert __enter__ to float

Post by samneggs » Sat Mar 05, 2022 6:18 pm

Not very practical but you could probably get it to work with _thread if you rewrite your code using integer math instead of floating point and not dynamically create objects in the main loop. I've had solid success with using _thread but the above compromises combined with the inherent complexity of threading usually does not justify using it.
If you continuously monitor gc.mem_free() and it does not decrease, _thread will probably work.

Sam

Post Reply