-
Notifications
You must be signed in to change notification settings - Fork 315
Description
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()
.
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
.