🦀

はんだ付けから始めるEmbedded Rust on Espressif(2)

2023/12/07に公開

温湿度の取得

前回まではLEDを光らせてきました。
光り物はもう十分なので他のことをやります。

これは温湿度が取得できるDHT11センサーです。
これを利用して温湿度を取得してみます。

https://akizukidenshi.com/catalog/g/gM-07003/

以下のように回路を組みます。

ちょうど同じことをやっている方がいるので新しくプロジェクトを作成してそのままコードをコピペします。

https://www.youtube.com/watch?v=5qYswqbZUDs
https://github.com/shanemmattner/ESP32-C3_Rust_Tutorials/tree/main/Tutorials/p4-dht11

コードは以下のようになりました。

use dht11::Dht11;
use esp_idf_svc::hal::{
    delay::{Ets, FreeRtos},
    gpio::*,
    prelude::Peripherals,
};

fn main() {
    // It is necessary to call this function once. Otherwise some patches to the runtime
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
    esp_idf_svc::sys::link_patches();

    // Bind the log crate to the ESP Logging facilities
    esp_idf_svc::log::EspLogger::initialize_default();

    let peripherals = Peripherals::take().unwrap();
    let dht11_pin = PinDriver::input_output_od(peripherals.pins.gpio5.downgrade()).unwrap();
    let mut dht11 = Dht11::new(dht11_pin);

    loop {
        let mut dht11_delay = Ets;
        match dht11.perform_measurement(&mut dht11_delay) {
            Ok(m) => println!(
                "temp: {}C, humidity: {}%",
                (m.temperature as f32 / 10.0),
                (m.humidity as f32 / 10.0)
            ),
            Err(e) => println!("{:?}", e),
        }
        FreeRtos::delay_ms(1000);
    }
}

実行するとちゃんと温湿度の値が取得できるようになりました。

$ cargo run
   Compiling esp-idf-hal v0.42.5
   Compiling esp-idf-sys v0.33.7
   Compiling dht11 v0.1.0 (/home/satoken/work/rust/dht11)
   Compiling esp-idf-svc v0.47.3
   Compiling wifi v0.1.0 (/home/satoken/work/rust/dht11/lib/wifi)
    Finished dev [optimized + debuginfo] target(s) in 9.87s
     Running `espflash flash --monitor target/riscv32imc-esp-espidf/debug/dht11`
[2023-12-06T15:37:57Z INFO ] Serial port: '/dev/ttyACM0'
[2023-12-06T15:37:57Z INFO ] Connecting...
[2023-12-06T15:37:58Z INFO ] Using flash stub
[2023-12-06T15:37:58Z WARN ] Setting baud rate higher than 115,200 can cause issues
Chip type:         esp32c3 (revision v0.4)
Crystal frequency: 40MHz
Flash size:        4MB
Features:          WiFi, BLE
MAC address:       a0:76:4e:b3:87:60
App/part. size:    532,064/4,128,768 bytes, 12.89%
[00:00:00] [========================================]      13/13      0x0                                                         [00:00:00] [========================================]       1/1       0x8000                                                      [00:00:04] [========================================]     257/257     0x10000                                                     [2023-12-06T15:38:03Z INFO ] Flashing has completed!
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xd (SPI_FAST_FLASH_BOOT)
Saved PC:0x40380836
0x40380836 - read_id_core
    at /home/satoken/work/rust/dht11/.embuild/espressif/esp-idf/v5.1.1/components/spi_flash/esp_flash_api.c:435
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd5820,len:0x171c
0x3fcd5820 - _heap_start
    at ??:??
load:0x403cc710,len:0x968
0x403cc710 - _iram_text_end
    at ??:??
load:0x403ce710,len:0x2f68
0x403ce710 - _iram_text_end
    at ??:??
SHA-256 comparison failed:
Calculated: 1d06b938c0222bf626e0bdf46178b1b37ab24d03f0360fc8fcf7153c2571deaf
Expected: 68d7bdf643ba446b8ed7ae8423241d442fd052b2bc77091100ba06fd65dcf8d5
Attempting to boot anyway...
entry 0x403cc710
0x403cc710 - _iram_text_end
    at ??:??
I (43) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (43) boot: compile time Jun  7 2023 07:59:10
I (44) boot: chip revision: v0.4
I (48) boot.esp32c3: SPI Speed      : 40MHz
I (53) boot.esp32c3: SPI Mode       : DIO
I (57) boot.esp32c3: SPI Flash Size : 4MB
I (62) boot: Enabling RNG early entropy source...
I (68) boot: Partition Table:
I (71) boot: ## Label            Usage          Type ST Offset   Length
I (78) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (86) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (93) boot:  2 factory          factory app      00 00 00010000 003f0000
I (101) boot: End of partition table
I (105) esp_image: segment 0: paddr=00010020 vaddr=3c060020 size=21118h (135448) map
I (143) esp_image: segment 1: paddr=00031140 vaddr=3fc8a000 size=01088h (  4232) load
I (145) esp_image: segment 2: paddr=000321d0 vaddr=40380000 size=09edch ( 40668) load
I (159) esp_image: segment 3: paddr=0003c0b4 vaddr=00000000 size=03f64h ( 16228)
I (163) esp_image: segment 4: paddr=00040020 vaddr=42000020 size=51e14h (335380) map
I (242) boot: Loaded app from partition at offset 0x10000
I (242) boot: Disabling RNG early entropy source...
I (253) cpu_start: Unicore app
I (254) cpu_start: Pro cpu up.
I (262) cpu_start: Pro cpu start user code
I (262) cpu_start: cpu freq: 160000000 Hz
I (263) cpu_start: Application information:
I (266) cpu_start: Project name:     libespidf
I (271) cpu_start: App version:      1
I (275) cpu_start: Compile time:     Nov 30 2023 18:24:00
I (281) cpu_start: ELF file SHA256:  0000000000000000...
I (287) cpu_start: ESP-IDF:          v5.1.1
I (292) cpu_start: Min chip rev:     v0.3
I (297) cpu_start: Max chip rev:     v0.99
I (302) cpu_start: Chip rev:         v0.4
I (306) heap_init: Initializing. RAM available for dynamic allocation:
I (314) heap_init: At 3FC8C200 len 00050510 (321 KiB): DRAM
I (320) heap_init: At 3FCDC710 len 00002950 (10 KiB): STACK/DRAM
I (326) heap_init: At 50000010 len 00001FD8 (7 KiB): RTCRAM
I (334) spi_flash: detected chip: generic
I (337) spi_flash: flash io: dio
W (342) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (350) sleep: Configure to isolate all GPIO pins in sleep state
I (357) sleep: Enable automatic switching of GPIO sleep configuration
I (364) app_start: Starting scheduler on CPU0
I (369) main_task: Started on CPU0
I (369) main_task: Calling app_main()
I (369) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
temp: 21C, humidity: 45%
temp: 20C, humidity: 47%
temp: 20C, humidity: 46%
temp: 20C, humidity: 46%

温湿度APIサーバ

ESP32シリーズはWifiに接続してHTTPやMQTTを使用することが可能です。
HTTPサーバを立ててESP32から温湿度の値を返す簡単なAPIサーバにしてみます。

std-trainingのhttp-serverフォルダのコードを流用します。
git cloneしたらdht11のドライバやjsonを扱うためにserdeをCargo.tomlに追記します。
cfg.tomlファイルに自宅のWifiのSSIDとパスワードを書きます。

https://github.com/esp-rs/std-training/tree/main/intro/http-server

以下のようなコードになりました。

use anyhow::Result;
use core::str;
use embedded_svc::{http::Method, io::Write};
use esp_idf_svc::{
    eventloop::EspSystemEventLoop,
    hal::{
        prelude::*,
    },
    http::server::{Configuration, EspHttpServer},
    hal::delay::Ets,
    hal::gpio::{IOPin, PinDriver},
};
use std::{
    sync::{Arc, Mutex},
    thread::sleep,
    time::Duration,
};
use wifi::wifi;
use dht11::Dht11;

#[toml_cfg::toml_config]
pub struct Config {
    #[default("")]
    wifi_ssid: &'static str,
    #[default("")]
    wifi_psk: &'static str,
}

use serde::{Serialize};

#[derive(Serialize)]
struct Dht11Info {
    temp: String,
    humidity: String,
}

fn main() -> Result<()> {
    esp_idf_svc::sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();

    let peripherals = Peripherals::take().unwrap();
    let sysloop = EspSystemEventLoop::take()?;

    let dht11_pin = PinDriver::input_output_od(peripherals.pins.gpio5.downgrade()).unwrap();
    let dht11 = Dht11::new(dht11_pin);
    let dht11 = Arc::new(Mutex::new(dht11));

    // The constant `CONFIG` is auto-generated by `toml_config`.
    let app_config = CONFIG;

    // Connect to the Wi-Fi network
    let _wifi = wifi(
        app_config.wifi_ssid,
        app_config.wifi_psk,
        peripherals.modem,
        sysloop,
    )?;
    
    // Set the HTTP server
    let mut server = EspHttpServer::new(&Configuration::default())?;
    // http://<sta ip>/ handler
    server.fn_handler("/", Method::Get, move |request| {
        let mut dht11_result = Dht11Info{ temp: String::new(), humidity: String::new() };
        let mut dht11_delay = Ets;
        match dht11.lock().unwrap().perform_measurement(&mut dht11_delay) {
            Ok(m) => {
                dht11_result.temp = format!("{}C", m.temperature as f32 / 10.0);
                dht11_result.humidity = format!("{}%", m.humidity as f32 / 10.0);
            },
            Err(e) => println!("{:?}", e),
        }

        let mut response = request.into_ok_response()?;
        let json = serde_json::to_string(&dht11_result).unwrap();
        response.write_all(json.as_ref())?;
        Ok(())
    })?;

    // This is not true until you actually create one
    println!("Server awaiting connection");

    // Prevent program from exiting
    loop {
        sleep(Duration::from_millis(1000));
    }
}

コードを解説します。
JSONレスポンス用の構造体です。

#[derive(Serialize)]
struct Dht11Info {
    temp: String,
    humidity: String,
}

DHT11を接続したピンを指定して初期化します。

    let dht11_pin = PinDriver::input_output_od(peripherals.pins.gpio5.downgrade()).unwrap();
    let dht11 = Dht11::new(dht11_pin);
    let dht11 = Arc::new(Mutex::new(dht11));

Wifiに接続します。

    // Connect to the Wi-Fi network
    let _wifi = wifi(
        app_config.wifi_ssid,
        app_config.wifi_psk,
        peripherals.modem,
        sysloop,
    )?;

HTTPサーバを作成してハンドラをセットします。
"/"にアクセスが来たときに、DHT11から温湿度を読み取りJSONにしてレスポンスを返します。

    // Set the HTTP server
    let mut server = EspHttpServer::new(&Configuration::default())?;
    // http://<sta ip>/ handler
    server.fn_handler("/", Method::Get, move |request| {
        let mut dht11_result = Dht11Info{ temp: String::new(), humidity: String::new() };
        let mut dht11_delay = Ets;
        match dht11.lock().unwrap().perform_measurement(&mut dht11_delay) {
            Ok(m) => {
                dht11_result.temp = format!("{}C", m.temperature as f32 / 10.0);
                dht11_result.humidity = format!("{}%", m.humidity as f32 / 10.0);
            },
            Err(e) => println!("{:?}", e),
        }

        let mut response = request.into_ok_response()?;
        let json = serde_json::to_string(&dht11_result).unwrap();
        response.write_all(json.as_ref())?;
        Ok(())
    })?;

作成したプログラムを実行します。

$ cargo run
   Compiling http-server v0.1.0 (/home/satoken/work/rust/std-training/intro/http-server)
    Finished dev [optimized + debuginfo] target(s) in 1.44s
     Running `espflash flash --monitor target/riscv32imc-esp-espidf/debug/http-server`
[2023-12-06T15:23:42Z INFO ] Serial port: '/dev/ttyACM0'
[2023-12-06T15:23:42Z INFO ] Connecting...
[2023-12-06T15:23:42Z INFO ] Using flash stub
[2023-12-06T15:23:42Z WARN ] Setting baud rate higher than 115,200 can cause issues
Chip type:         esp32c3 (revision v0.4)
Crystal frequency: 40MHz
Flash size:        4MB
Features:          WiFi, BLE
MAC address:       a0:76:4e:b3:87:60
App/part. size:    867,696/4,128,768 bytes, 21.02%
[00:00:00] [========================================]      13/13      0x0                                                         [00:00:00] [========================================]       1/1       0x8000                                                      [00:00:07] [========================================]     523/523     0x10000                                                     [2023-12-06T15:23:51Z INFO ] Flashing has completed!
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xd (SPI_FAST_FLASH_BOOT)
Saved PC:0x40380832
0x40380832 - read_id_core
    at /home/satoken/.espressif/esp-idf/v5.1.2/components/spi_flash/esp_flash_api.c:434
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd5820,len:0x171c
0x3fcd5820 - _bss_end
    at ??:??
load:0x403cc710,len:0x968
0x403cc710 - _iram_text_end
    at ??:??
load:0x403ce710,len:0x2f68
0x403ce710 - _iram_text_end
    at ??:??
SHA-256 comparison failed:
Calculated: 1d06b938c0222bf626e0bdf46178b1b37ab24d03f0360fc8fcf7153c2571deaf
Expected: 68d7bdf643ba446b8ed7ae8423241d442fd052b2bc77091100ba06fd65dcf8d5
Attempting to boot anyway...
entry 0x403cc710
0x403cc710 - _iram_text_end
    at ??:??
I (43) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (43) boot: compile time Jun  7 2023 07:59:10
I (44) boot: chip revision: v0.4
I (48) boot.esp32c3: SPI Speed      : 40MHz
I (53) boot.esp32c3: SPI Mode       : DIO
I (57) boot.esp32c3: SPI Flash Size : 4MB
I (62) boot: Enabling RNG early entropy source...
I (68) boot: Partition Table:
I (71) boot: ## Label            Usage          Type ST Offset   Length
I (78) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (86) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (93) boot:  2 factory          factory app      00 00 00010000 003f0000
I (101) boot: End of partition table
I (105) esp_image: segment 0: paddr=00010020 vaddr=3c0a0020 size=23e38h (147000) map
I (146) esp_image: segment 1: paddr=00033e60 vaddr=3fc8ee00 size=02914h ( 10516) load
I (149) esp_image: segment 2: paddr=0003677c vaddr=40380000 size=0989ch ( 39068) load
I (161) esp_image: segment 3: paddr=00040020 vaddr=42000020 size=9e888h (649352) map
I (304) esp_image: segment 4: paddr=000de8b0 vaddr=4038989c size=05494h ( 21652) load
I (314) boot: Loaded app from partition at offset 0x10000
I (314) boot: Disabling RNG early entropy source...
I (326) cpu_start: Unicore app
I (326) cpu_start: Pro cpu up.
I (335) cpu_start: Pro cpu start user code
I (335) cpu_start: cpu freq: 160000000 Hz
I (335) cpu_start: Application information:
I (338) cpu_start: Project name:     libespidf
I (343) cpu_start: App version:      32f879f-dirty
I (348) cpu_start: Compile time:     Nov 29 2023 01:34:26
I (354) cpu_start: ELF file SHA256:  0000000000000000...
I (360) cpu_start: ESP-IDF:          v5.1.2
I (365) cpu_start: Min chip rev:     v0.3
I (370) cpu_start: Max chip rev:     v0.99
I (375) cpu_start: Chip rev:         v0.4
I (380) heap_init: Initializing. RAM available for dynamic allocation:
I (387) heap_init: At 3FC95950 len 0002A6B0 (169 KiB): DRAM
I (393) heap_init: At 3FCC0000 len 0001C710 (113 KiB): DRAM/RETENTION
I (400) heap_init: At 3FCDC710 len 00002950 (10 KiB): DRAM/RETENTION/STACK
I (408) heap_init: At 50000010 len 00001FD8 (7 KiB): RTCRAM
I (415) spi_flash: detected chip: generic
I (419) spi_flash: flash io: dio
W (423) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (431) sleep: Configure to isolate all GPIO pins in sleep state
I (438) sleep: Enable automatic switching of GPIO sleep configuration
I (445) app_start: Starting scheduler on CPU0
I (450) main_task: Started on CPU0
I (450) main_task: Calling app_main()
I (450) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (460) pp: pp rom version: 9387209
I (470) net80211: net80211 rom version: 9387209
I (480) wifi:wifi driver task: 3fca10a8, prio:23, stack:6656, core=0
I (480) wifi:wifi firmware version: 91b9630
I (480) wifi:wifi certification version: v7.0
I (490) wifi:config NVS flash: disabled
I (490) wifi:config nano formating: disabled
I (490) wifi:Init data frame dynamic rx buffer num: 32
I (500) wifi:Init static rx mgmt buffer num: 10
I (500) wifi:Init management short buffer num: 32
I (510) wifi:Init dynamic tx buffer num: 32
I (510) wifi:Init static tx FG buffer num: 2
I (520) wifi:Init static rx buffer size: 1600
I (520) wifi:Init static rx buffer num: 10
I (520) wifi:Init dynamic rx buffer num: 32
I (530) wifi_init: rx ba win: 6
I (530) wifi_init: tcpip mbox: 32
I (530) wifi_init: udp mbox: 6
I (540) wifi_init: tcp mbox: 6
I (540) wifi_init: tcp tx win: 5744
I (550) wifi_init: tcp rx win: 5744
I (550) wifi_init: tcp mss: 1440
I (550) wifi_init: WiFi IRAM OP enabled
I (560) wifi_init: WiFi RX IRAM OP enabled
I (570) wifi: Starting wifi...
I (570) phy_init: phy_version 1130,b4e4b80,Sep  5 2023,11:09:30
E (570) phy_init: esp_phy_load_cal_data_from_nvs: NVS has not been initialized. Call nvs_flash_init before starting WiFi/BT.
W (590) phy_init: failed to load RF calibration data (0x1101), falling back to full calibration
I (630) wifi:mode : sta (a0:76:4e:b3:87:60)
I (630) wifi:enable tsf
I (630) wifi: Scanning...
I (5740) wifi: Found configured access point xxxxxxxx on channel 5
I (5740) wifi: Connecting wifi...
I (8150) wifi:new:<5,0>, old:<1,0>, ap:<255,255>, sta:<5,0>, prof:1
I (8420) wifi:state: init -> auth (b0)
I (8430) wifi:state: auth -> assoc (0)
I (8450) wifi:state: assoc -> run (10)
I (8560) wifi:connected with xxxxxxxx, aid = 3, channel 5, BW20, bssid = 1c:3b:f3:95:6a:2c
I (8560) wifi:security: WPA2-PSK, phy: bgn, rssi: -51
I (8560) wifi:pm start, type: 1

I (8560) wifi:set rx beacon pti, rx_bcn_pti: 0, bcn_timeout: 25000, mt_pti: 0, mt_time: 10000
I (8580) wifi: Waiting for DHCP lease...
I (8630) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (9660) wifi:<ba-add>idx:0 (ifx:0, 1c:3b:f3:95:6a:2c), tid:0, ssn:176, winSize:64
I (10580) esp_netif_handlers: sta ip: 192.168.0.190, mask: 255.255.255.0, gw: 192.168.0.1
I (10580) wifi: Wifi DHCP info: IpInfo { ip: 192.168.0.190, subnet: Subnet { gateway: 192.168.0.1, mask: Mask(24) }, dns: Some(219.110.4.84), secondary_dns: Some(219.110.2.24) }
I (10590) esp_idf_svc::http::server: Started Httpd server with config Configuration { http_port: 80, https_port: 443, max_sessions: 16, session_timeout: 1200s, stack_size: 6144, max_open_sockets: 4, max_uri_handlers: 32, max_resp_handlers: 8, lru_purge_enable: true, uri_match_wildcard: false }
I (10620) esp_idf_svc::http::server: Registered Httpd server handler Get for URI "/"
Server awaiting connection
Timeout

ESP32に割り当てられたIPアドレスにcurlコマンドを実行するとセンサーから取得した温湿度の値がJSONで返ってくるようになりました。

$ curl -s http://192.168.0.190 | jq .
{
  "temp": "21C",
  "humidity": "45%"
}

Discussion