Skip to content

Wrong distance values when using Distance Sensor with MockFactory #877

@wallysalami

Description

@wallysalami

Operating system: Windows 10 / macOS 10.15.4
Python version: 3.7.7
Pi model: none
Pin factory used: MockFactory
GPIO Zero version: 1.5.1

Due to the Coronavirus outbreak, my engineering students cannot use the laboratory. So I decided to build a device simulator in TkInter, using Device.pin_factory = MockFactory().

Simulator

TkInter's Button acts like physical buttons, LED images react to the state of pins, etc. But when I try to use a TkInter's Scale (slider) to represent the Distance Sensor, I get terrible distance values. Here is the gist of my TkInter code:

Device.pin_factory = MockFactory()
self._echo_pin = Device.pin_factory.pin(17)
self._trigger_pin = Device.pin_factory.pin(18, pin_class=MockTriggerPin, echo_pin=self._echo_pin)
self._scale = Scale(root, from_=0, to=100, orient=HORIZONTAL, command=self._scale_changed)

def _scale_changed(self, value):
    speed_of_sound = 343.26 # m/s
    distance = float(value) / 100 # cm -> m
    self._trigger_pin.echo_time = distance * 2 / speed_of_sound

And here is the gist of gpiozero code:

from gpiozero import Buzzer, LED, Button, DistanceSensor
sensor = DistanceSensor(trigger=17, echo=18)
sensor.max_distance = 5
print(sensor.distance)

The sensor.distance value is ok-ish in macOS, but it gets really bad in Windows 10 – the minimum value is around 2.5 meters.

So I started to study gpiozero's classes. After some tests, I think I found the problem:

class LocalPiFactory(PiFactory):
    @staticmethod
    def ticks():
        return monotonic() # <-- PROBLEM!!!

class MockTriggerPin(MockPin):
    def _echo(self):
        sleep(0.001)
        self.echo_pin.drive_high()
        sleep(self.echo_time)  # <-- PROBLEM!!!
        self.echo_pin.drive_low()

In my tests, I found out that monotonic() and sleep() are not very precise for small intervals, specially on Windows. Instead of that, I think we should use clock() (deprecated) or perf_counter() (starting on Python 3.3), both from time library.

Here is a proposed solution (I can create a pull request if you so wish):

class LocalPiFactory(PiFactory):
    @staticmethod
    def ticks():
        return perf_counter()

class MockTriggerPin(MockPin):
    def _echo(self):
        sleep(0.001)
        self.echo_pin.drive_high()
        
        init_time = perf_counter()
        while True:
            if perf_counter() - init_time >= self.echo_time:
                break

        self.echo_pin.drive_low()

We could also fix ticks() in MockFactory instead of LocalPiFactory.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions