> {
3 | embuild::build::CfgArgs::output_propagated("ESP_IDF")?;
4 | embuild::build::LinkArgs::output_propagated("ESP_IDF")?;
5 | Ok(())
6 | }
7 |
--------------------------------------------------------------------------------
/WIP/ch06-wifi-mqtt/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly"
--------------------------------------------------------------------------------
/WIP/ch06-wifi-mqtt/sdkconfig.defaults:
--------------------------------------------------------------------------------
1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000
3 |
4 | # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
5 | # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
6 | #CONFIG_FREERTOS_HZ=1000
7 |
8 | # Workaround for https://github.com/espressif/esp-idf/issues/7631
9 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
10 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
11 |
--------------------------------------------------------------------------------
/WIP/ch06-wifi-mqtt/src/cloud.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Result};
2 | use embedded_svc::mqtt::client::{Connection, Event, MessageImpl, QoS};
3 | use embedded_svc::{utils::mqtt::client::ConnState, wifi::*};
4 | use esp_idf_hal::peripheral;
5 | use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
6 | use esp_idf_svc::{eventloop::*, netif::*, wifi::*};
7 | use esp_idf_sys::esp_efuse_mac_get_default;
8 | use esp_idf_sys::EspError;
9 | use esp_println::println;
10 | use serde::Serialize;
11 | use std::{env, thread, time::Duration};
12 |
13 | const SSID: &str = env!("WIFI_SSID");
14 | const PASS: &str = env!("WIFI_PASS");
15 |
16 | #[derive(Serialize, Debug)]
17 | struct MqttData {
18 | distance: u16,
19 | temperature: f32,
20 | tds: f32,
21 | }
22 |
23 | #[cfg(not(feature = "qemu"))]
24 | pub fn wifi(
25 | modem: impl peripheral::Peripheral + 'static,
26 | sysloop: EspSystemEventLoop,
27 | ) -> Result>> {
28 | use std::net::Ipv4Addr;
29 |
30 | let mut wifi = Box::new(EspWifi::new(modem, sysloop.clone(), None)?);
31 |
32 | println!("Wifi created, about to scan");
33 |
34 | let ap_infos = wifi.scan()?;
35 |
36 | let ours = ap_infos.into_iter().find(|a| a.ssid == SSID);
37 |
38 | let channel = if let Some(ours) = ours {
39 | println!(
40 | "Found configured access point {} on channel {}",
41 | SSID, ours.channel
42 | );
43 | Some(ours.channel)
44 | } else {
45 | println!(
46 | "Configured access point {} not found during scanning, will go with unknown channel",
47 | SSID
48 | );
49 | None
50 | };
51 |
52 | wifi.set_configuration(&Configuration::Client(ClientConfiguration {
53 | ssid: SSID.into(),
54 | password: PASS.into(),
55 | channel,
56 | ..Default::default()
57 | }))?;
58 |
59 | wifi.start()?;
60 |
61 | println!("Starting wifi...");
62 |
63 | if !WifiWait::new(&sysloop)?
64 | .wait_with_timeout(Duration::from_secs(20), || wifi.is_started().unwrap())
65 | {
66 | bail!("Wifi did not start");
67 | }
68 |
69 | println!("Connecting wifi...");
70 |
71 | wifi.connect()?;
72 |
73 | if !EspNetifWait::new::(wifi.sta_netif(), &sysloop)?.wait_with_timeout(
74 | Duration::from_secs(20),
75 | || {
76 | wifi.is_connected().unwrap()
77 | && wifi.sta_netif().get_ip_info().unwrap().ip != Ipv4Addr::new(0, 0, 0, 0)
78 | },
79 | ) {
80 | bail!("Wifi did not connect or did not receive a DHCP lease");
81 | }
82 |
83 | let ip_info = wifi.sta_netif().get_ip_info()?;
84 |
85 | println!("Wifi DHCP info: {:?}", ip_info);
86 |
87 | Ok(wifi)
88 | }
89 |
90 | pub fn get_client(url: &str) -> Result>, EspError> {
91 | let client_id = format!("fishtank-rust_{}", get_unique_id());
92 | let conf = MqttClientConfiguration {
93 | client_id: Some(&client_id),
94 | keep_alive_interval: Some(std::time::Duration::new(60, 0)),
95 | lwt: Some(LwtConfiguration {
96 | topic: "fishtank/status",
97 | payload: b"offline",
98 | qos: QoS::AtLeastOnce,
99 | retain: true,
100 | }),
101 | ..Default::default()
102 | };
103 |
104 | let (mut client, mut connection) = EspMqttClient::new_with_conn(url, &conf).unwrap();
105 |
106 | thread::spawn(move || {
107 | while let Some(msg) = connection.next() {
108 | let event = msg.as_ref().unwrap();
109 | match event {
110 | Event::Received(_msg) => {}
111 | Event::Connected(_) => {}
112 | Event::Disconnected => {}
113 | Event::Subscribed(_x) => {
114 | // Do nothing
115 | }
116 | _event => println!("Got unknown MQTT event"),
117 | }
118 | }
119 | });
120 | client
121 | .publish("fishtank/status", QoS::AtLeastOnce, true, b"online")
122 | .unwrap();
123 | Ok(client)
124 | }
125 |
126 | pub fn get_unique_id() -> String {
127 | let mut mac: [u8; 6] = [0; 6];
128 | unsafe {
129 | let ptr = &mut mac as *mut u8;
130 | esp_efuse_mac_get_default(ptr);
131 | }
132 | hex::encode(mac)
133 | }
134 |
135 | fn publish_data(data: MqttData, client: &mut EspMqttClient>) {
136 | let data = serde_json::to_string(&data).unwrap();
137 | client
138 | .publish("fishtank/sensors", QoS::AtLeastOnce, false, data.as_bytes())
139 | .unwrap();
140 | }
141 |
--------------------------------------------------------------------------------
/WIP/ch06-wifi-mqtt/src/tasks.rs:
--------------------------------------------------------------------------------
1 | use crossbeam_utils::atomic::AtomicCell;
2 | use esp_idf_hal::{
3 | adc::{self, *},
4 | gpio::{ADCPin, AnyIOPin, Input, PinDriver},
5 | ledc::LedcDriver,
6 | };
7 | use esp_println::println;
8 | use std::{sync::Arc, thread, time::Duration};
9 |
10 | static ADC_MAX_COUNTS: u32 = 2850;
11 |
12 | pub fn adc_thread(
13 | adc_mutex: Arc>,
14 | mut adc: AdcDriver,
15 | mut pin: adc::AdcChannelDriver>,
16 | ) where
17 | Atten11dB: Attenuation<::Adc>,
18 | {
19 | loop {
20 | // Read ADC and and set the LED PWM to the percentage of full scale
21 | match adc.read(&mut pin) {
22 | Ok(x) => {
23 | adc_mutex.store(x);
24 | }
25 |
26 | Err(e) => println!("err reading ADC: {e}\n"),
27 | }
28 |
29 | thread::sleep(Duration::from_millis(100));
30 | }
31 | }
32 |
33 | // Thread function that will blink the LED on/off every 500ms
34 | pub fn blinky_thread(
35 | rx: crossbeam_channel::Receiver,
36 | adc_mutex: Arc>,
37 | mut channel: LedcDriver<'_>,
38 | ) {
39 | let mut blinky_status = false;
40 | let max_duty = channel.get_max_duty();
41 | loop {
42 | // Watch for button press messages
43 | match rx.try_recv() {
44 | Ok(x) => {
45 | blinky_status = x;
46 | }
47 | Err(_) => {}
48 | }
49 |
50 | // blinky if the button was pressed
51 | if blinky_status {
52 | match channel.set_duty(0) {
53 | Ok(_x) => (),
54 | Err(e) => println!("err setting duty of led: {e}\n"),
55 | }
56 | println!("LED ON");
57 | thread::sleep(Duration::from_millis(1000));
58 |
59 | match channel.set_duty(max_duty) {
60 | Ok(_x) => (),
61 | Err(e) => println!("err setting duty of led: {e}\n"),
62 | }
63 | println!("LED OFF");
64 | thread::sleep(Duration::from_millis(1000));
65 | } else {
66 | let duty = adc_mutex.load() as u32;
67 | let pwm = (duty as u32 * max_duty) / ADC_MAX_COUNTS;
68 | match channel.set_duty(pwm) {
69 | Ok(_x) => (),
70 | Err(e) => println!("err setting duty of led: {e}\n"),
71 | }
72 | }
73 |
74 | thread::sleep(Duration::from_millis(100));
75 | }
76 | }
77 |
78 | pub fn button_thread(btn_pin: PinDriver, tx: crossbeam_channel::Sender) {
79 | let mut btn_status = false;
80 |
81 | loop {
82 | if btn_pin.is_high() {
83 | if !btn_status {
84 | btn_status = true;
85 | println!("BUTTON ON");
86 | tx.send(btn_status).unwrap();
87 | }
88 | } else {
89 | if btn_status {
90 | btn_status = false;
91 | println!("BUTTON OFF");
92 | tx.send(btn_status).unwrap();
93 | }
94 | }
95 | thread::sleep(Duration::from_millis(100));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/WIP/ch06-wifi-mqtt/src/wifi.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Context};
2 | use embedded_svc::wifi::{ClientConfiguration, Configuration};
3 | use esp_idf_hal::modem::WifiModem;
4 | use esp_idf_svc::{
5 | eventloop::EspEventLoop,
6 | netif::{EspNetif, EspNetifWait},
7 | nvs::EspDefaultNvsPartition,
8 | wifi::{EspWifi, WifiWait},
9 | };
10 | use esp_println::println;
11 | use std::{net::Ipv4Addr, time::Duration};
12 |
13 | pub fn connect(wifi_ssid: &str, wifi_pass: &str) -> anyhow::Result> {
14 | let sys_loop = EspEventLoop::take().unwrap();
15 | let modem = unsafe { WifiModem::new() };
16 | let nvs = EspDefaultNvsPartition::take().unwrap();
17 | let mut wifi = EspWifi::new(modem, sys_loop.clone(), Some(nvs))?;
18 |
19 | println!("Wifi created, scanning available networks...");
20 |
21 | let available_networks = wifi.scan()?;
22 | let target_network = available_networks
23 | .iter()
24 | .find(|network| network.ssid == wifi_ssid)
25 | .with_context(|| format!("Failed to detect the target network ({wifi_ssid})"))?;
26 |
27 | println!("Scan successfull, found '{wifi_ssid}', with config: {target_network:#?}");
28 |
29 | wifi.set_configuration(&Configuration::Client(ClientConfiguration {
30 | ssid: wifi_ssid.into(),
31 | password: wifi_pass.into(),
32 | auth_method: target_network.auth_method,
33 | bssid: Some(target_network.bssid),
34 | channel: Some(target_network.channel),
35 | }))?;
36 |
37 | wifi.start()?;
38 | if !WifiWait::new(&sys_loop)?
39 | .wait_with_timeout(Duration::from_secs(20), || wifi.is_started().unwrap())
40 | {
41 | bail!("Wifi did not start");
42 | }
43 |
44 | wifi.connect()?;
45 |
46 | if !EspNetifWait::new::(wifi.sta_netif(), &sys_loop)?.wait_with_timeout(
47 | Duration::from_secs(20),
48 | || {
49 | wifi.driver().is_connected().unwrap()
50 | && wifi.sta_netif().get_ip_info().unwrap().ip != Ipv4Addr::new(0, 0, 0, 0)
51 | },
52 | ) {
53 | bail!("Wifi did not connect or did not receive a DHCP lease");
54 | }
55 |
56 | let ip_info = wifi.sta_netif().get_ip_info()?;
57 |
58 | println!("Wifi DHCP info: {:?}", ip_info);
59 |
60 | Ok(wifi)
61 | }
62 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | # Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3)
3 | #target = "xtensa-esp32-espidf"
4 | #target = "xtensa-esp32s2-espidf"
5 | #target = "xtensa-esp32s3-espidf"
6 | target = "riscv32imc-esp-espidf"
7 |
8 | [target.xtensa-esp32-espidf]
9 | linker = "ldproxy"
10 | runner = "espflash flash --monitor"
11 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
12 |
13 | [target.xtensa-esp32s2-espidf]
14 | linker = "ldproxy"
15 | runner = "espflash flash --monitor"
16 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
17 |
18 | [target.xtensa-esp32s3-espidf]
19 | linker = "ldproxy"
20 | runner = "espflash flash --monitor"
21 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
22 |
23 | [target.riscv32imc-esp-espidf]
24 | linker = "ldproxy"
25 | runner = "espflash flash --monitor"
26 | # Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3. See also https://github.com/ivmarkov/embuild/issues/16
27 | # For ESP-IDF 5 add `espidf_time64` and for earlier versions - remove this flag: https://github.com/esp-rs/rust/issues/110
28 | rustflags = ["-C", "default-linker-libraries"]
29 |
30 | [unstable]
31 |
32 | build-std = ["std", "panic_abort"]
33 | #build-std-features = ["panic_immediate_abort"] # Required for older ESP-IDF versions without a realpath implementation
34 |
35 | [env]
36 | # Note: these variables are not used when using pio builder (`cargo build --features pio`)
37 | # Builds against ESP-IDF stable (v4.4)
38 | ESP_IDF_VERSION = "release/v4.4"
39 | # Builds against ESP-IDF master (mainline)
40 | #ESP_IDF_VERSION = "master"
41 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode
2 | /.embuild
3 | /target
4 | /Cargo.lock
5 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wifi-mqtt"
3 | version = "0.1.0"
4 | authors = ["Shane"]
5 | edition = "2021"
6 | resolver = "2"
7 |
8 | [profile.release]
9 | opt-level = "s"
10 |
11 | [profile.dev]
12 | debug = true # Symbols are nice and they don't increase the size on Flash
13 | opt-level = "z"
14 |
15 | [features]
16 | pio = ["esp-idf-sys/pio"]
17 |
18 | [dependencies]
19 | esp-idf-sys = { version = "0.32", features = ["binstart"] }
20 | esp-idf-hal = "0.40"
21 | esp-println = { version = "0.3.1", features = ["esp32c3"] }
22 | esp-idf-svc = "0.45"
23 | embedded-svc = "0.24"
24 | statig = "0.2.0"
25 | crossbeam = "0.8"
26 | serde = { version = "1", features = ["derive"] }
27 | hex = "0.4.3"
28 | crossbeam-channel = "0.5"
29 | anyhow = { version = "1", features = ["backtrace"] }
30 |
31 |
32 | [build-dependencies]
33 | embuild = "0.31"
34 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/README.md:
--------------------------------------------------------------------------------
1 | # Blinky ADC
2 |
3 | In this example we'll use ADC values to blinky the LED at a certain PWM based on the ADC reading.
4 |
5 | Now that we have more tasks building up we should move them to another file `tasks.rs`. To use
6 | those tasks in main we'll just import with:
7 |
8 | ```rust
9 | mod tasks;
10 | ```
11 |
12 | And to use `led_fsm.rs` functions in `tasks.rs` we'll need to use `led_fsm`:
13 | ```rust
14 | use crate::led_fsm;
15 | ```
16 |
17 |
18 |
19 |
20 | ## setup debugging
21 | https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32c3/api-guides/jtag-debugging/index.html
22 |
23 | ## TODO
24 | 3. incorporate the led pwm into the current blinky state machine
25 | a. mutex for adc readings
26 | b. When it's time to blink look at the ADC reading and calculate a pwm
27 |
28 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/build.rs:
--------------------------------------------------------------------------------
1 | // Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641
2 | fn main() -> Result<(), Box> {
3 | embuild::build::CfgArgs::output_propagated("ESP_IDF")?;
4 | embuild::build::LinkArgs::output_propagated("ESP_IDF")?;
5 | Ok(())
6 | }
7 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly"
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/sdkconfig.defaults:
--------------------------------------------------------------------------------
1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000
3 |
4 | # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
5 | # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
6 | #CONFIG_FREERTOS_HZ=1000
7 |
8 | # Workaround for https://github.com/espressif/esp-idf/issues/7631
9 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
10 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
11 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/src/cloud.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Result};
2 | use embedded_svc::mqtt::client::{Connection, Event, MessageImpl, QoS};
3 | use embedded_svc::{utils::mqtt::client::ConnState, wifi::*};
4 | use esp_idf_hal::peripheral;
5 | use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
6 | use esp_idf_svc::{eventloop::*, netif::*, wifi::*};
7 | use esp_idf_sys::esp_efuse_mac_get_default;
8 | use esp_idf_sys::EspError;
9 | use esp_println::println;
10 | use std::{env, thread, time::Duration};
11 |
12 | const SSID: &str = env!("WIFI_SSID");
13 | const PASS: &str = env!("WIFI_PASS");
14 | const _MQTT_URL: &str = env!("MQTT_URL");
15 |
16 | #[cfg(not(feature = "qemu"))]
17 | pub fn wifi(
18 | modem: impl peripheral::Peripheral + 'static,
19 | sysloop: EspSystemEventLoop,
20 | ) -> Result>> {
21 | use std::net::Ipv4Addr;
22 |
23 | let mut wifi = Box::new(EspWifi::new(modem, sysloop.clone(), None)?);
24 |
25 | println!("Wifi created, about to scan");
26 |
27 | let ap_infos = wifi.scan()?;
28 |
29 | let ours = ap_infos.into_iter().find(|a| a.ssid == SSID);
30 |
31 | let channel = if let Some(ours) = ours {
32 | println!(
33 | "Found configured access point {} on channel {}",
34 | SSID, ours.channel
35 | );
36 | Some(ours.channel)
37 | } else {
38 | println!(
39 | "Configured access point {} not found during scanning, will go with unknown channel",
40 | SSID
41 | );
42 | None
43 | };
44 |
45 | wifi.set_configuration(&Configuration::Client(ClientConfiguration {
46 | ssid: SSID.into(),
47 | password: PASS.into(),
48 | channel,
49 | ..Default::default()
50 | }))?;
51 |
52 | wifi.start()?;
53 |
54 | println!("Starting wifi...");
55 |
56 | if !WifiWait::new(&sysloop)?
57 | .wait_with_timeout(Duration::from_secs(20), || wifi.is_started().unwrap())
58 | {
59 | bail!("Wifi did not start");
60 | }
61 |
62 | println!("Connecting wifi...");
63 |
64 | wifi.connect()?;
65 |
66 | if !EspNetifWait::new::(wifi.sta_netif(), &sysloop)?.wait_with_timeout(
67 | Duration::from_secs(20),
68 | || {
69 | wifi.is_connected().unwrap()
70 | && wifi.sta_netif().get_ip_info().unwrap().ip != Ipv4Addr::new(0, 0, 0, 0)
71 | },
72 | ) {
73 | bail!("Wifi did not connect or did not receive a DHCP lease");
74 | }
75 |
76 | let ip_info = wifi.sta_netif().get_ip_info()?;
77 |
78 | println!("Wifi DHCP info: {:?}", ip_info);
79 |
80 | Ok(wifi)
81 | }
82 |
83 | pub fn get_client(url: &str) -> Result>, EspError> {
84 | let client_id = format!("fishtank-rust_{}", get_unique_id());
85 | let conf = MqttClientConfiguration {
86 | client_id: Some(&client_id),
87 | keep_alive_interval: Some(std::time::Duration::new(60, 0)),
88 | lwt: Some(LwtConfiguration {
89 | topic: "fishtank/status",
90 | payload: b"offline",
91 | qos: QoS::AtLeastOnce,
92 | retain: true,
93 | }),
94 | ..Default::default()
95 | };
96 |
97 | let (mut client, mut connection) = EspMqttClient::new_with_conn(url, &conf).unwrap();
98 |
99 | thread::spawn(move || {
100 | while let Some(msg) = connection.next() {
101 | let event = msg.as_ref().unwrap();
102 | match event {
103 | Event::Received(_msg) => {}
104 | Event::Connected(_) => {}
105 | Event::Disconnected => {}
106 | Event::Subscribed(_x) => {
107 | // Do nothing
108 | }
109 | _event => println!("Got unknown MQTT event"),
110 | }
111 | }
112 | });
113 | client
114 | .publish("fishtank/status", QoS::AtLeastOnce, true, b"online")
115 | .unwrap();
116 | Ok(client)
117 | }
118 |
119 | pub fn get_unique_id() -> String {
120 | let mut mac: [u8; 6] = [0; 6];
121 | unsafe {
122 | let ptr = &mut mac as *mut u8;
123 | esp_efuse_mac_get_default(ptr);
124 | }
125 | hex::encode(mac)
126 | }
127 |
128 | fn publish_data(data: MqttData, client: &mut EspMqttClient>) {
129 | let data = serde_json::to_string(&data).unwrap();
130 | client
131 | .publish("fishtank/sensors", QoS::AtLeastOnce, false, data.as_bytes())
132 | .unwrap();
133 | }
134 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/src/led_fsm.rs:
--------------------------------------------------------------------------------
1 | use esp_idf_hal::gpio::{AnyOutputPin, Output, PinDriver};
2 | use esp_println::println;
3 | use statig::prelude::*;
4 |
5 | // #[derive(Debug, Default)]
6 | pub struct Blinky<'a> {
7 | pub led_pin: PinDriver<'a, AnyOutputPin, Output>,
8 | }
9 |
10 | // The event that will be handled by the state machine.
11 | pub enum Event {
12 | TimerElapsed,
13 | ButtonPressed,
14 | }
15 | #[derive(Debug)]
16 | pub enum State {
17 | LedOn,
18 | LedOff,
19 | NotBlinking,
20 | }
21 |
22 | pub enum Superstate {
23 | Blinking,
24 | }
25 |
26 | impl StateMachine for Blinky<'_> {
27 | type State = State;
28 | type Superstate<'a> = Superstate;
29 | type Event<'a> = Event;
30 | const INITIAL: State = State::LedOff;
31 | const ON_TRANSITION: fn(&mut Self, &Self::State, &Self::State) = |_, source, target| {
32 | println!("Transitioned from {source:?} to {target:?}");
33 | };
34 | }
35 |
36 | impl statig::State> for State {
37 | fn call_handler(&mut self, _blinky: &mut Blinky, event: &Event) -> Response {
38 | match self {
39 | State::LedOn => Blinky::led_on(event),
40 | State::LedOff => Blinky::led_off(event),
41 | State::NotBlinking => Blinky::not_blinking(event),
42 | }
43 | }
44 |
45 | fn superstate(&mut self) -> Option {
46 | match self {
47 | State::LedOn => Some(Superstate::Blinking),
48 | State::LedOff => Some(Superstate::Blinking),
49 | State::NotBlinking => None,
50 | }
51 | }
52 |
53 | fn call_entry_action(&mut self, blinky: &mut Blinky) {
54 | match self {
55 | State::LedOn => blinky.enter_led_on(),
56 | State::LedOff => blinky.enter_led_off(),
57 | _ => (),
58 | }
59 | }
60 | }
61 |
62 | impl statig::Superstate> for Superstate {
63 | fn call_handler(&mut self, _blinky: &mut Blinky, event: &Event) -> Response {
64 | match self {
65 | Superstate::Blinking => Blinky::blinking(event),
66 | }
67 | }
68 | }
69 |
70 | impl Blinky<'_> {
71 | fn enter_led_on(&mut self) {
72 | self.led_pin.set_high().unwrap();
73 | }
74 |
75 | fn enter_led_off(&mut self) {
76 | self.led_pin.set_low().unwrap();
77 | }
78 |
79 | fn led_on(event: &Event) -> Response {
80 | match event {
81 | Event::TimerElapsed => Transition(State::LedOff),
82 | _ => Super,
83 | }
84 | }
85 |
86 | fn led_off(event: &Event) -> Response {
87 | match event {
88 | Event::TimerElapsed => Transition(State::LedOn),
89 | _ => Super,
90 | }
91 | }
92 |
93 | fn blinking(event: &Event) -> Response {
94 | match event {
95 | Event::ButtonPressed => Transition(State::NotBlinking),
96 | _ => Super,
97 | }
98 | }
99 |
100 | fn not_blinking(event: &Event) -> Response {
101 | match event {
102 | Event::ButtonPressed => Transition(State::LedOn),
103 | _ => Super,
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/src/main.rs:
--------------------------------------------------------------------------------
1 | extern crate crossbeam;
2 | extern crate crossbeam_channel;
3 |
4 | use crossbeam_channel::bounded;
5 | use esp_idf_hal::{
6 | adc::{self, AdcDriver},
7 | gpio::{InputPin, PinDriver, *},
8 | ledc::{config::TimerConfig, *},
9 | prelude::*,
10 | };
11 | use esp_idf_svc::eventloop::*;
12 | use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
13 | use esp_println::println;
14 | use serde::Serialize;
15 | use statig::prelude::*;
16 | use std::sync::Arc;
17 |
18 | mod cloud;
19 | mod led_fsm;
20 | mod tasks;
21 |
22 | #[derive(Serialize, Debug)]
23 | struct MqttData {
24 | distance: u16,
25 | temperature: f32,
26 | tds: f32,
27 | }
28 |
29 | const MQTT_URL: &str = env!("MQTT_URL");
30 |
31 | fn main() {
32 | // It is necessary to call this function once. Otherwise some patches to the runtime
33 | // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
34 | esp_idf_sys::link_patches();
35 |
36 | let peripherals = Peripherals::take().unwrap();
37 | let (tx1, rx1) = bounded(1); // make channel to pass data
38 |
39 | // Config GPIO for input and output that will be passed to FSM
40 | let led_pin = PinDriver::output(peripherals.pins.gpio8.downgrade_output()).unwrap();
41 | let btn_pin = PinDriver::input(peripherals.pins.gpio6.downgrade_input()).unwrap();
42 | // TODO: how to make the `btn` pin pull-up or pull-down.
43 | let led_fsm = led_fsm::Blinky { led_pin }.state_machine().init();
44 |
45 | // LED controller config
46 | let config = TimerConfig::new().frequency(25.kHz().into());
47 | let timer = Arc::new(LedcTimerDriver::new(peripherals.ledc.timer0, &config).unwrap());
48 | let channel0 = LedcDriver::new(
49 | peripherals.ledc.channel0,
50 | timer.clone(),
51 | peripherals.pins.gpio7,
52 | )
53 | .unwrap();
54 | let max_duty = channel0.get_max_duty();
55 | // ADC config to change LED pwm
56 | let a1_ch4 =
57 | adc::AdcChannelDriver::<_, adc::Atten11dB>::new(peripherals.pins.gpio4).unwrap();
58 | let adc1 = AdcDriver::new(
59 | peripherals.adc1,
60 | &adc::config::Config::new().calibration(true),
61 | )
62 | .unwrap();
63 |
64 | // Start WIFI and get MQTT client
65 | let sysloop = EspSystemEventLoop::take().unwrap();
66 | let _wifi = cloud::wifi(peripherals.modem, sysloop).unwrap();
67 | let mut _client = cloud::get_client(MQTT_URL).unwrap();
68 |
69 | // Initialize threads
70 | let _blinky_thread = std::thread::Builder::new()
71 | .stack_size(tasks::BLINKY_STACK_SIZE)
72 | .spawn(move || tasks::blinky_fsm_thread(led_fsm, rx1))
73 | .unwrap();
74 |
75 | let _button_thread = std::thread::Builder::new()
76 | .stack_size(tasks::BUTTON_STACK_SIZE)
77 | .spawn(move || tasks::button_thread(btn_pin, tx1))
78 | .unwrap();
79 |
80 | let _adc_thread = std::thread::Builder::new()
81 | .stack_size(tasks::ADC_STACK_SIZE)
82 | .spawn(move || tasks::adc_thread(adc1, a1_ch4, max_duty, channel0))
83 | .unwrap();
84 | }
85 |
--------------------------------------------------------------------------------
/WIP/ch07-blinky-mqtt/src/tasks.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 | #![allow(unused_variables, unused_imports)]
3 | extern crate crossbeam;
4 | extern crate crossbeam_channel;
5 |
6 | use crossbeam_channel::bounded;
7 | use esp_idf_hal::ledc::config::TimerConfig;
8 | use esp_idf_hal::{
9 | adc::{self, config::Config, AdcDriver, Atten11dB, *},
10 | gpio::{
11 | self, AnyInputPin, AnyOutputPin, Input, InputMode, InputPin, Output, OutputPin, PinDriver,
12 | *,
13 | },
14 | ledc::*,
15 | prelude::*,
16 | };
17 | use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
18 | use esp_println::println;
19 | use statig::{prelude::*, InitializedStatemachine};
20 | use std::{
21 | sync::{mpsc::*, Arc},
22 | thread,
23 | time::Duration,
24 | };
25 |
26 | use crate::led_fsm;
27 |
28 | pub static ADC_MAX_COUNTS: u32 = 2850;
29 | pub static BLINKY_STACK_SIZE: usize = 2000;
30 | pub static BUTTON_STACK_SIZE: usize = 2000;
31 | pub static ADC_STACK_SIZE: usize = 5000;
32 |
33 | pub fn button_thread(
34 | btn_pin: PinDriver<'_, AnyInputPin, Input>,
35 | tx: crossbeam_channel::Sender,
36 | ) {
37 | let mut btn_state = true;
38 | loop {
39 | if btn_pin.is_high() {
40 | if !btn_state {
41 | btn_state = true;
42 | tx.send(btn_state).unwrap();
43 | }
44 | } else {
45 | btn_state = false;
46 | }
47 |
48 | thread::sleep(Duration::from_millis(100));
49 | }
50 | }
51 |
52 | pub fn blinky_fsm_thread(
53 | mut fsm: InitializedStatemachine,
54 | rx: crossbeam_channel::Receiver,
55 | ) {
56 | loop {
57 | fsm.handle(&led_fsm::Event::TimerElapsed);
58 | match rx.try_recv() {
59 | Ok(_) => fsm.handle(&led_fsm::Event::ButtonPressed),
60 | Err(_) => {}
61 | }
62 |
63 | thread::sleep(Duration::from_millis(1000));
64 | }
65 | }
66 |
67 | pub fn adc_thread(
68 | mut adc: AdcDriver,
69 | mut pin: adc::AdcChannelDriver>,
70 | max_duty: u32,
71 | mut channel: LedcDriver<'_>,
72 | ) where
73 | Atten11dB: Attenuation<::Adc>,
74 | {
75 | loop {
76 | // Read ADC and and set the LED PWM to the percentage of full scale
77 | match adc.read(&mut pin) {
78 | Ok(x) => {
79 | let pwm = (x as u32 * max_duty) / ADC_MAX_COUNTS;
80 | match channel.set_duty(pwm) {
81 | Ok(x) => (),
82 | Err(e) => println!("err setting duty of led: {e}\n"),
83 | }
84 | }
85 | Err(e) => println!("err reading ADC: {e}\n"),
86 | }
87 |
88 | thread::sleep(Duration::from_millis(100));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/WIP/p4-neopixel/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | # Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3)
3 | #target = "xtensa-esp32-espidf"
4 | #target = "xtensa-esp32s2-espidf"
5 | #target = "xtensa-esp32s3-espidf"
6 | target = "riscv32imc-esp-espidf"
7 |
8 | [target.xtensa-esp32-espidf]
9 | linker = "ldproxy"
10 | runner = "espflash flash --monitor"
11 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
12 |
13 | [target.xtensa-esp32s2-espidf]
14 | linker = "ldproxy"
15 | runner = "espflash flash --monitor"
16 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
17 |
18 | [target.xtensa-esp32s3-espidf]
19 | linker = "ldproxy"
20 | runner = "espflash flash --monitor"
21 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
22 |
23 | [target.riscv32imc-esp-espidf]
24 | linker = "ldproxy"
25 | runner = "espflash flash --monitor"
26 | # Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3. See also https://github.com/ivmarkov/embuild/issues/16
27 | # For ESP-IDF 5 add `espidf_time64` and for earlier versions - remove this flag: https://github.com/esp-rs/rust/issues/110
28 | rustflags = ["-C", "default-linker-libraries"]
29 |
30 | [unstable]
31 |
32 | build-std = ["std", "panic_abort"]
33 | #build-std-features = ["panic_immediate_abort"] # Required for older ESP-IDF versions without a realpath implementation
34 |
35 | [env]
36 | # Note: these variables are not used when using pio builder (`cargo build --features pio`)
37 | # Builds against ESP-IDF stable (v4.4)
38 | ESP_IDF_VERSION = "release/v4.4"
39 | # Builds against ESP-IDF master (mainline)
40 | #ESP_IDF_VERSION = "master"
41 |
--------------------------------------------------------------------------------
/WIP/p4-neopixel/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode
2 | /.embuild
3 | /target
4 | /Cargo.lock
5 |
--------------------------------------------------------------------------------
/WIP/p4-neopixel/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "neopixel"
3 | version = "0.1.0"
4 | authors = ["Shane Mattner "]
5 | edition = "2021"
6 | resolver = "2"
7 |
8 | [profile.release]
9 | opt-level = "s"
10 |
11 | [profile.dev]
12 | debug = true # Symbols are nice and they don't increase the size on Flash
13 | opt-level = "z"
14 |
15 | [features]
16 | pio = ["esp-idf-sys/pio"]
17 |
18 | [dependencies]
19 | esp-idf-sys = { version = "0.32", features = ["binstart"] }
20 | esp-idf-hal = "0.40"
21 | esp-println = { version = "0.3.1", features = ["esp32c3"] }
22 | anyhow = "1"
23 |
24 | [build-dependencies]
25 | embuild = "0.31"
26 |
--------------------------------------------------------------------------------
/WIP/p4-neopixel/build.rs:
--------------------------------------------------------------------------------
1 | // Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641
2 | fn main() -> Result<(), Box> {
3 | embuild::build::CfgArgs::output_propagated("ESP_IDF")?;
4 | embuild::build::LinkArgs::output_propagated("ESP_IDF")?;
5 | Ok(())
6 | }
7 |
--------------------------------------------------------------------------------
/WIP/p4-neopixel/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly"
--------------------------------------------------------------------------------
/WIP/p4-neopixel/sdkconfig.defaults:
--------------------------------------------------------------------------------
1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000
3 |
4 | # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
5 | # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
6 | #CONFIG_FREERTOS_HZ=1000
7 |
8 | # Workaround for https://github.com/espressif/esp-idf/issues/7631
9 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
10 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
11 |
--------------------------------------------------------------------------------
/WIP/wifi/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | # Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3)
3 | #target = "xtensa-esp32-espidf"
4 | #target = "xtensa-esp32s2-espidf"
5 | #target = "xtensa-esp32s3-espidf"
6 | target = "riscv32imc-esp-espidf"
7 |
8 | [target.xtensa-esp32-espidf]
9 | linker = "ldproxy"
10 | runner = "espflash flash --monitor"
11 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
12 |
13 | [target.xtensa-esp32s2-espidf]
14 | linker = "ldproxy"
15 | runner = "espflash flash --monitor"
16 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
17 |
18 | [target.xtensa-esp32s3-espidf]
19 | linker = "ldproxy"
20 | runner = "espflash flash --monitor"
21 | #rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
22 |
23 | [target.riscv32imc-esp-espidf]
24 | linker = "ldproxy"
25 | runner = "espflash flash --monitor"
26 | # Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3. See also https://github.com/ivmarkov/embuild/issues/16
27 | # For ESP-IDF 5 add `espidf_time64` and for earlier versions - remove this flag: https://github.com/esp-rs/rust/issues/110
28 | rustflags = ["-C", "default-linker-libraries"]
29 |
30 | [unstable]
31 |
32 | build-std = ["std", "panic_abort"]
33 | #build-std-features = ["panic_immediate_abort"] # Required for older ESP-IDF versions without a realpath implementation
34 |
35 | [env]
36 | # Note: these variables are not used when using pio builder (`cargo build --features pio`)
37 | # Builds against ESP-IDF stable (v4.4)
38 | ESP_IDF_VERSION = "release/v4.4"
39 | # Builds against ESP-IDF master (mainline)
40 | #ESP_IDF_VERSION = "master"
41 |
--------------------------------------------------------------------------------
/WIP/wifi/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode
2 | /.embuild
3 | /target
4 | /Cargo.lock
5 |
--------------------------------------------------------------------------------
/WIP/wifi/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "blinky-wifi"
3 | version = "0.1.0"
4 | authors = ["Shane Mattner "]
5 | edition = "2021"
6 | resolver = "2"
7 |
8 | [profile.release]
9 | opt-level = "s"
10 |
11 | [profile.dev]
12 | debug = true # Symbols are nice and they don't increase the size on Flash
13 | opt-level = "z"
14 |
15 | [features]
16 | pio = ["esp-idf-sys/pio"]
17 |
18 | [dependencies]
19 | esp-idf-sys = { version = "0.31", features = ["binstart"] }
20 | esp-idf-hal = "0.38"
21 | embedded-hal = "0.2"
22 | esp-idf-svc="0.42"
23 | nb = "1.0.0"
24 | log="0.4"
25 | embedded-svc = "0.22"
26 | anyhow = "1"
27 | statig = "0.2.0"
28 |
29 |
30 | [build-dependencies]
31 | embuild = "0.30.4"
32 | anyhow = "1"
33 |
34 |
--------------------------------------------------------------------------------
/WIP/wifi/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | export RUST_ESP32_STD_DEMO_WIFI_SSID='yourSSID'
3 | export RUST_ESP32_STD_DEMO_WIFI_PASS='yourPASS'
4 | cargo build
5 | espflash /dev/ttyACM0 target/riscv32imc-esp-espidf/debug/ch04-blinky-wifi
6 | espmonitor /dev/ttyACM0
7 | ```
--------------------------------------------------------------------------------
/WIP/wifi/build.rs:
--------------------------------------------------------------------------------
1 | // Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641
2 | fn main() -> Result<(), Box> {
3 | embuild::build::CfgArgs::output_propagated("ESP_IDF")?;
4 | embuild::build::LinkArgs::output_propagated("ESP_IDF")?;
5 | Ok(())
6 | }
7 |
--------------------------------------------------------------------------------
/WIP/wifi/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly"
--------------------------------------------------------------------------------
/WIP/wifi/sdkconfig.defaults:
--------------------------------------------------------------------------------
1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000
3 |
4 | # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
5 | # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
6 | #CONFIG_FREERTOS_HZ=1000
7 |
8 | # Workaround for https://github.com/espressif/esp-idf/issues/7631
9 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
10 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
11 |
--------------------------------------------------------------------------------
/WIP/wifi/src/main.rs:
--------------------------------------------------------------------------------
1 | use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
2 |
3 | use anyhow::bail;
4 | use anyhow::Result;
5 | use embedded_hal::digital::v2::OutputPin;
6 | use embedded_svc::wifi::*;
7 | use esp_idf_hal::{gpio::*, prelude::*};
8 | use esp_idf_svc::netif::*;
9 | use esp_idf_svc::nvs::*;
10 | use esp_idf_svc::wifi::*;
11 | use esp_idf_svc::wifi::*;
12 | use statig::prelude::*;
13 | use std::sync::Arc;
14 | use std::{thread, time::Duration};
15 |
16 | mod wifi;
17 |
18 | static BLINKY_STACK_SIZE: usize = 5000;
19 |
20 | fn main() {
21 | // It is necessary to call this function once. Otherwise some patches to the runtime
22 | // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
23 | esp_idf_sys::link_patches();
24 |
25 | let peripherals = Peripherals::take().unwrap();
26 |
27 | match wifi::test_wifi() {
28 | Ok(x) => println!("IP: {}", x),
29 | Err(x) => println!("{}", x),
30 | }
31 | // let mut led = PinDriver::output(peripherals.pins.gpio8).unwrap();
32 | // let mut state_machine = Blinky { led }.state_machine().init();
33 |
34 | // let _blinky_thread = std::thread::Builder::new()
35 | // .stack_size(BLINKY_STACK_SIZE)
36 | // .spawn(move || loop {
37 | // thread::sleep(Duration::from_millis(500));
38 | // state_machine.handle(&Event::TimerElapsed);
39 | // thread::sleep(Duration::from_millis(500));
40 | // state_machine.handle(&Event::TimerElapsed);
41 | // })
42 | // .unwrap();
43 | }
44 |
--------------------------------------------------------------------------------
/WIP/wifi/src/wifi.rs:
--------------------------------------------------------------------------------
1 | #![allow(unused_imports, dead_code)]
2 | use anyhow::bail;
3 | use anyhow::Result;
4 | use embedded_svc::ipv4;
5 | use embedded_svc::ping::Ping;
6 | use embedded_svc::wifi::*;
7 | use esp_idf_svc::netif::*;
8 | use esp_idf_svc::nvs::*;
9 | use esp_idf_svc::ping;
10 | use esp_idf_svc::sntp;
11 | use esp_idf_svc::sysloop::*;
12 | use esp_idf_svc::wifi::*;
13 | use std::{cell::RefCell, env, sync::atomic::*, sync::Arc, thread, time::*};
14 |
15 | const WIFI_SSID: &str = env!("RUST_ESP32_STD_WIFI_SSID");
16 | const WIFI_PASS: &str = env!("RUST_ESP32_STD_WIFI_PASS");
17 |
18 | pub fn test_wifi() -> Result {
19 | let netif_stack = Arc::new(EspNetifStack::new()?);
20 | let sys_look_stack = Arc::new(EspSysLoopStack::new()?);
21 | let nvs = Arc::new(EspDefaultNvs::new()?);
22 |
23 | let mut wifi = EspWifi::new(netif_stack, sys_look_stack, nvs)?;
24 |
25 | wifi.set_configuration(&Configuration::Client(ClientConfiguration {
26 | ssid: WIFI_SSID.into(),
27 | password: WIFI_PASS.into(),
28 | ..Default::default()
29 | }))?;
30 |
31 | wifi.wait_status_with_timeout(Duration::from_secs(30), |s| !s.is_transitional())
32 | .map_err(|e| anyhow::anyhow!("Wait timeout: {:?}", e))?;
33 |
34 | let status = wifi.get_status();
35 |
36 | println!("Status: {:?}", status);
37 |
38 | if let ClientStatus::Started(ClientConnectionStatus::Connected(ClientIpStatus::Done(
39 | client_settings,
40 | ))) = status.0
41 | {
42 | Ok(format!("{:?}", client_settings.ip))
43 | } else {
44 | Err(anyhow::anyhow!("Failed to connect in time."))
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docs/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shanemmattner/ESP32-C3_Rust_Tutorials/abe228df29f93bb43d22aee3868eb8bb2b03574a/docs/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
--------------------------------------------------------------------------------
/docs/ESP32-C3-DevKit-Lipo_Rev_B.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shanemmattner/ESP32-C3_Rust_Tutorials/abe228df29f93bb43d22aee3868eb8bb2b03574a/docs/ESP32-C3-DevKit-Lipo_Rev_B.pdf
--------------------------------------------------------------------------------
/docs/PSiCC2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shanemmattner/ESP32-C3_Rust_Tutorials/abe228df29f93bb43d22aee3868eb8bb2b03574a/docs/PSiCC2.pdf
--------------------------------------------------------------------------------