drivers/dht improvements (take 2)

C programming, build, interpreter/VM.
Target audience: MicroPython Developers.
User avatar
MostlyHarmless
Posts: 147
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: drivers/dht improvements (take 2)

Post by MostlyHarmless » Sat Jan 04, 2020 2:11 pm

tve wrote:
Sat Jan 04, 2020 1:32 am
you have been using a model where the driver exports an `ameasure` method that calls async delay functions inside. In my drivers I have instead provided a `start` method that starts the data acquisition and returns the expected time it will take and then a second `read` method that reads the result assuming it's ready and returns an error if it's not. The rationale for my design is that this model lets the caller decide which form of delay function to call. Is that not preferable?
It is perfectly preferable. Especially since it does not restrict itself to one type of multitasking framework. I like that.

What format and unit is it that your `start` method returns the desired delay? It would be good to coordinate things like that across drivers.


Thanks, Jan

User avatar
tve
Posts: 104
Joined: Wed Jan 01, 2020 10:12 pm
Location: Santa Barbara, CA
Contact:

Re: drivers/dht improvements (take 2)

Post by tve » Sat Jan 04, 2020 11:19 pm

The start() or convert() method (I can't decide what the name should be) returns milliseconds to wait. I picked that for expediency more than anything else. Less than one ms and it seems better to just sleep right there...
I believe where "my" model breaks is when there are multiple steps or multiple options. For example, with the Si7021 temp/hum sensor one can start a temperature conversion and then read the result. One can also separately start a humidity conversion and read that result, plus the temperature which gets captured as well (used internally to compensate the humidity). That's still simple, but the start_this() and start_that() methods begin to accumulate.
I tend to hack up drivers I find to cut out the 90% I don't need and to make sure the 10% I do need work the way I expect, so I don't encounter complex situations that often.
Another tricky example is a driver for UART based devices, such as many particle matter sensors. These don't really have any predictable timing. I ended up providing a read() method that internally processes however many chars are available and returns None when no complete packet has arrived yet or the data when it has. But that's unsatisfactory also because it means it needs to be called all the time which makes it difficult to sleep in low power state in the main loop.

User avatar
MostlyHarmless
Posts: 147
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: drivers/dht improvements (take 2)

Post by MostlyHarmless » Sun Jan 05, 2020 3:20 am

tve wrote:
Sat Jan 04, 2020 11:19 pm
The start() or convert() method (I can't decide what the name should be) returns milliseconds to wait. I picked that for expediency more than anything else. Less than one ms and it seems better to just sleep right there...
Agreed, ms makes sense. Will change the model tomorrow.
tve wrote:
Sat Jan 04, 2020 11:19 pm
I believe where "my" model breaks is when there are multiple steps or multiple options. For example, with the Si7021 temp/hum sensor one can start a temperature conversion and then read the result. One can also separately start a humidity conversion and read that result, plus the temperature which gets captured as well (used internally to compensate the humidity). That's still simple, but the start_this() and start_that() methods begin to accumulate.
How about start(CONV_TEMP | CONV_HUM | ...) ?
tve wrote:
Sat Jan 04, 2020 11:19 pm
Another tricky example is a driver for UART based devices, such as many particle matter sensors. These don't really have any predictable timing. I ended up providing a read() method that internally processes however many chars are available and returns None when no complete packet has arrived yet or the data when it has. But that's unsatisfactory also because it means it needs to be called all the time which makes it difficult to sleep in low power state in the main loop.
Yeah, I got something similar here. A NEO-6M GPS module. It sends NMEA on UART, and then later comes the PPS. Like "at the tone it is <time> ... BEEP." And it does that all the time, not just when asked for. The problem with that is that if you don't constantly drain the UART, you get garbage on the first read because the UART's receive buffer has overflown. So I have one async task constantly reading the data and remembering the last $GRMC record seen. Then when one wants the time one just waits for the rising edge of the PPS and decodes the last record. The task reading the UART is using a uasyncio.StreamReader(), which works quite well.


Regards, Jan

User avatar
MostlyHarmless
Posts: 147
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: drivers/dht improvements (take 2)

Post by MostlyHarmless » Sun Jan 05, 2020 4:59 pm

MostlyHarmless wrote:
Sun Jan 05, 2020 3:20 am
Agreed, ms makes sense. Will change the model tomorrow.
Changed the driver accordingly. Here is the new implementation that removes the uasyncio dependency.

The example, added to the esp8266 and esp32 Quickref documention reads:
Separate methods to send the start signal and receive the result
exist to minimize blocking in event loops. This is a uasyncio
based example printing the sensor data every 2 seconds:

Code: Select all

    import dht
    import machine
    import uasyncio as asyncio

    async def report_dht_data(d):
        while True:
            await asyncio.sleep(2)
            try:
                wait_ms = d.start()
                await asyncio.sleep_ms(wait_ms)
                d.receive()
            except Exception as ex:
                print("error: {}".format(ex))
                continue

            print("temp: {}°C humi: {}%RH".format(d.temperature(),
                                                  d.humidity()))

    d = dht.DHT22(machine.Pin(4))
    loop = asyncio.get_event_loop()
    loop.create_task(report_dht_data(d))
    loop.run_forever()
Everything is rebased on current master and tested on esp8266 and esp32. Thanks again for the suggestion, @tve.


Regards, Jan

User avatar
mcauser
Posts: 444
Joined: Mon Jun 15, 2015 8:03 am

Re: drivers/dht improvements (take 2)

Post by mcauser » Fri Jan 17, 2020 3:27 am

The AM2320 is an upgrade of the DHT22 / AM2302, and it supports both the old 1-wire interface and I2C.
https://github.com/mcauser/micropython-am2320

Same goes for the tiny DHT12, which is an upgrade of the DHT11, and adds I2C too.
https://github.com/mcauser/micropython-dht12

These could do with some asyncio.

User avatar
pythoncoder
Posts: 4025
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: drivers/dht improvements (take 2)

Post by pythoncoder » Fri Jan 17, 2020 9:31 am

There is nothing wrong with

Code: Select all

wait_ms = d.start()
await asyncio.sleep_ms(wait_ms)
d.receive()
however if I were writing the device driver I'd either provide an awaitable receive method so you could write

Code: Select all

await d.receive()
or make the class an awaitable class. Then you'd just have

Code: Select all

await d
Either approach hides the detail of how the delay is performed (enabling it perhaps to be changed at some point) without affecting application behaviour.
Peter Hinch

User avatar
MostlyHarmless
Posts: 147
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: drivers/dht improvements (take 2)

Post by MostlyHarmless » Fri Jan 17, 2020 1:02 pm

pythoncoder wrote:
Fri Jan 17, 2020 9:31 am
There is nothing wrong with

Code: Select all

wait_ms = d.start()
await asyncio.sleep_ms(wait_ms)
d.receive()
however if I were writing the device driver I'd either provide an awaitable receive method so you could write

Code: Select all

await d.receive()
or make the class an awaitable class. Then you'd just have

Code: Select all

await d
Either approach hides the detail of how the delay is performed (enabling it perhaps to be changed at some point) without affecting application behaviour.
The initial proposal did

Code: Select all

await d.ameasure()
I changed it to this separate start+receive model based on the discussion with @tve above. While an awaitable class certainly is more convenient to someone, using asyncio already, it makes it rather difficult to use in an application with a different multiplexing model.


Regards, Jan

User avatar
MostlyHarmless
Posts: 147
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: drivers/dht improvements (take 2)

Post by MostlyHarmless » Fri Jan 17, 2020 1:30 pm

mcauser wrote:
Fri Jan 17, 2020 3:27 am
The AM2320 is an upgrade of the DHT22 / AM2302, and it supports both the old 1-wire interface and I2C.
https://github.com/mcauser/micropython-am2320

Same goes for the tiny DHT12, which is an upgrade of the DHT11, and adds I2C too.
https://github.com/mcauser/micropython-dht12

These could do with some asyncio.
Unless I missed something there seems to be only a single time.sleep_ms(2) in your code. Not sure where that delay is coming from but this is barely enough time for uasyncio to switch to another task and back. What happens if the other task takes a couple milliseconds? What happens if that other task performs I2C communication itself, like updating an OLED display? The latter one is especially hard to stress test.


Regards, Jan

jedie
Posts: 247
Joined: Fri Jan 29, 2016 12:32 pm
Contact:

Re: drivers/dht improvements (take 2)

Post by jedie » Fri Jan 17, 2020 3:35 pm

MostlyHarmless wrote:
Sun Jan 05, 2020 4:59 pm

Code: Select all

    import dht
    import machine
    import uasyncio as asyncio

    async def report_dht_data(d):
        while True:
            await asyncio.sleep(2)
            try:
                wait_ms = d.start()
                await asyncio.sleep_ms(wait_ms)
                d.receive()
            except Exception as ex:
                print("error: {}".format(ex))
                continue

            print("temp: {}°C humi: {}%RH".format(d.temperature(),
                                                  d.humidity()))

    d = dht.DHT22(machine.Pin(4))
    loop = asyncio.get_event_loop()
    loop.create_task(report_dht_data(d))
    loop.run_forever()
I would move the await asyncio.sleep(2) after the print() ... So you will get the temperature directly after start ;)

User avatar
MostlyHarmless
Posts: 147
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: drivers/dht improvements (take 2)

Post by MostlyHarmless » Fri Jan 17, 2020 3:52 pm

jedie wrote:
Fri Jan 17, 2020 3:35 pm
I would move the await asyncio.sleep(2) after the print() ... So you will get the temperature directly after start ;)
From a cold boot the DHT sensor needs some time to stabilize and become ready. If you try to query it too soon after power on all you get is a ETIMEDOUT. You might be able to move the two second sleep to the end of the loop but then end up needing another, smaller sleep before entering the loop.

Post Reply