Compare commits
40 Commits
923e7e0a59
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe4e48fbcc | ||
|
|
5e7285f45d | ||
|
|
5efb08ef99 | ||
|
|
d8b4352a0f | ||
|
|
70c37c344b | ||
|
|
d47060e5dd | ||
|
|
cb533fcdac | ||
|
|
02971bd9e1 | ||
|
|
e6e22818e4 | ||
|
|
4f21d03822 | ||
|
|
31bc689c69 | ||
|
|
1c0a5672b3 | ||
|
|
92d97206da | ||
|
|
e19a5853b5 | ||
|
|
8af5dcbede | ||
|
|
200f37d794 | ||
|
|
dc05d3ed37 | ||
|
|
1a0c27afb2 | ||
|
|
737b4b1dfe | ||
|
|
4e9566678a | ||
|
|
75f038262b | ||
|
|
3181081b09 | ||
|
|
e132980c82 | ||
|
|
83b6465dc6 | ||
|
|
b4e86bded6 | ||
|
|
73cbf7f912 | ||
|
|
e249c39c7b | ||
|
|
3e0801674f | ||
|
|
871c9bddd1 | ||
|
|
a3954ea3e8 | ||
|
|
948602bd1e | ||
|
|
cfcdb56fe8 | ||
|
|
99b1bf6c3d | ||
|
|
980fb67f85 | ||
|
|
0eab3bbaba | ||
|
|
7c5186ed49 | ||
|
|
2520db2fd7 | ||
|
|
bc436a5a3c | ||
|
|
f31e767b56 | ||
|
|
eb8488422e |
2
datasheet.txt
Normal file
2
datasheet.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
|
||||
https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf
|
||||
22
flake.nix
22
flake.nix
@@ -21,8 +21,10 @@
|
||||
gcc
|
||||
gnumake
|
||||
rustup
|
||||
rust-analyzer
|
||||
cargo-espflash
|
||||
espup
|
||||
mosquitto
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
@@ -33,16 +35,32 @@ shellHook = ''
|
||||
espup install
|
||||
fi
|
||||
|
||||
# Activate Xtensa Rust toolchain
|
||||
export RUSTUP_TOOLCHAIN=esp
|
||||
# Ensure the real rust-analyzer from Nix is used (not rustup's shim)
|
||||
export PATH=${pkgs.rust-analyzer}/bin:$PATH
|
||||
|
||||
# Keep ESP toolchain binaries on PATH for building/flashing
|
||||
# (do NOT set RUSTUP_TOOLCHAIN=esp globally; that breaks rust-analyzer)
|
||||
export PATH=$HOME/.rustup/toolchains/esp/bin:$PATH
|
||||
|
||||
# Add GCC/binutils path for xtensa-esp32-elf-gcc
|
||||
export PATH=$HOME/.rustup/toolchains/esp/xtensa-esp-elf/esp-14.2.0_20240906/xtensa-esp-elf/bin:$PATH
|
||||
|
||||
# Helpers that force the ESP toolchain explicitly when needed
|
||||
alias cargo-esp="RUSTUP_TOOLCHAIN=esp cargo"
|
||||
alias rustc-esp="RUSTUP_TOOLCHAIN=esp rustc"
|
||||
|
||||
echo "Xtensa Rust toolchain ready."
|
||||
rustc --version
|
||||
which xtensa-esp32-elf-gcc || echo "⚠️ xtensa-esp32-elf-gcc not found in PATH"
|
||||
|
||||
echo ""
|
||||
echo "MQTT broker test (run manually in two terminals):"
|
||||
echo " Terminal 1:"
|
||||
echo " mosquitto_sub -h mqtt.farmeris.sk -p 1883 -t esp32/topic -i subEsp -v"
|
||||
echo ""
|
||||
echo " Terminal 2:"
|
||||
echo " mosquitto_pub -h mqtt.farmeris.sk -p 1883 -t esp32/topic -m \"AAAAA\" -i pubEsp"
|
||||
echo ""
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
17
mqtt/.cargo/config.toml
Normal file
17
mqtt/.cargo/config.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
4
mqtt/.env_temp
Normal file
4
mqtt/.env_temp
Normal file
@@ -0,0 +1,4 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
BROKER_IP= "5.196.78.28"
|
||||
BROKER_PORT= "1883"
|
||||
20
mqtt/.gitignore
vendored
Normal file
20
mqtt/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1585
mqtt/Cargo.lock
generated
Normal file
1585
mqtt/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
90
mqtt/Cargo.toml
Normal file
90
mqtt/Cargo.toml
Normal file
@@ -0,0 +1,90 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "projekt_final"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "projekt_final"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
"esp32",
|
||||
"log-04",
|
||||
"unstable",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
|
||||
embassy-net = { version = "0.7.0", features = [
|
||||
"dhcpv4",
|
||||
"proto-ipv6",
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"tcp",
|
||||
"udp",
|
||||
] }
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.5.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
esp-wifi = { version = "0.15.0", features = [
|
||||
"builtin-scheduler",
|
||||
"esp-alloc",
|
||||
"esp32",
|
||||
"log-04",
|
||||
"smoltcp",
|
||||
"wifi",
|
||||
] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"proto-ipv6",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
rust-mqtt = { version = "0.3.0", default-features = false, features = ["no_std"] }
|
||||
defmt-rtt = "1.0.0"
|
||||
embassy-futures = "0.1.2"
|
||||
embassy-sync = "0.7.2"
|
||||
heapless = "0.9.1"
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
12
mqtt/Makefile
Normal file
12
mqtt/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
|
||||
info:
|
||||
espflash board-info
|
||||
73
mqtt/build.rs
Normal file
73
mqtt/build.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
fn main() {
|
||||
// Explicitly load .env from the project root, even when called from other dirs
|
||||
let project_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let dotenv_path = std::path::Path::new(&project_root).join(".env");
|
||||
if dotenvy::from_path(dotenv_path.clone()).is_ok() {
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
// Pass WIFI credentials into firmware
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
if let Ok(ip) = std::env::var("BROKER_IP") {
|
||||
println!("cargo:rustc-env=BROKER_IP={}", ip);
|
||||
}
|
||||
if let Ok(port) = std::env::var("BROKER_PORT") {
|
||||
println!("cargo:rustc-env=BROKER_PORT={}", port);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
mqtt/rust-toolchain.toml
Normal file
2
mqtt/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
181
mqtt/src/bin/main.rs
Normal file
181
mqtt/src/bin/main.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
// src/bin/main.rs
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types"
|
||||
)]
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_net::{Runner, StackResources};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_alloc as _;
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{clock::CpuClock, rng::Rng, timer::timg::TimerGroup};
|
||||
use esp_wifi::{
|
||||
init,
|
||||
wifi::{ClientConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiState},
|
||||
EspWifiController,
|
||||
};
|
||||
use log::info;
|
||||
use rust_mqtt::packet::v5::publish_packet::QualityOfService;
|
||||
use projekt_final::mqtt::client::{
|
||||
mqtt_events, mqtt_publish, mqtt_subscribe, mqtt_task, IncomingMsg,
|
||||
};
|
||||
use defmt_rtt as _;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
macro_rules! mk_static {
|
||||
($t:ty,$val:expr) => {{
|
||||
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||||
#[deny(unused_attributes)]
|
||||
let x = STATIC_CELL.uninit().write(($val));
|
||||
x
|
||||
}};
|
||||
}
|
||||
|
||||
const SSID: &str = env!("SSID");
|
||||
const PASSWORD: &str = env!("PASSWORD");
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 72 * 1024);
|
||||
|
||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
let mut rng = Rng::new(peripherals.RNG);
|
||||
|
||||
let esp_wifi_ctrl = &*mk_static!(
|
||||
EspWifiController<'static>,
|
||||
init(timg0.timer0, rng.clone()).unwrap()
|
||||
);
|
||||
|
||||
let (controller, interfaces) =
|
||||
esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).unwrap();
|
||||
|
||||
let wifi_interface = interfaces.sta;
|
||||
|
||||
let timg1 = TimerGroup::new(peripherals.TIMG1);
|
||||
esp_hal_embassy::init(timg1.timer0);
|
||||
|
||||
let config = embassy_net::Config::dhcpv4(Default::default());
|
||||
|
||||
let seed = (rng.random() as u64) << 32 | rng.random() as u64;
|
||||
|
||||
// Init network stack
|
||||
let (stack, runner) = embassy_net::new(
|
||||
wifi_interface,
|
||||
config,
|
||||
mk_static!(StackResources<3>, StackResources::<3>::new()),
|
||||
seed,
|
||||
);
|
||||
|
||||
spawner.spawn(connection(controller)).ok();
|
||||
spawner.spawn(net_task(runner)).ok();
|
||||
|
||||
// Wait for link up
|
||||
loop {
|
||||
if stack.is_link_up() {
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
info!("Waiting to get IP address...");
|
||||
loop {
|
||||
if let Some(config) = stack.config_v4() {
|
||||
info!("Got IP: {}", config.address);
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
spawner.spawn(mqtt_task(stack)).expect("failed to spawn MQTT task");
|
||||
info!("MQTT task started");
|
||||
|
||||
mqtt_publish("esp32/topic", b"hello from ESP32 (init)", QualityOfService::QoS1, false).await;
|
||||
info!("Sent initial MQTT message");
|
||||
|
||||
mqtt_subscribe("esp32/topic").await;
|
||||
|
||||
// Get a receiver for incoming MQTT messages
|
||||
let mqtt_rx = mqtt_events();
|
||||
|
||||
loop {
|
||||
// Drive both: either process an MQTT message or publish periodically
|
||||
match select(mqtt_rx.receive(), Timer::after(Duration::from_secs(5))).await
|
||||
{
|
||||
// Received inbound MQTT message (from broker)
|
||||
Either::First(msg) => {
|
||||
handle_incoming(msg);
|
||||
}
|
||||
// Time-based example publish
|
||||
Either::Second(_) => {
|
||||
// mqtt_publish(
|
||||
// "esp32/topic",
|
||||
// b"hello from main",
|
||||
// QualityOfService::QoS1,
|
||||
// false,
|
||||
// )
|
||||
// .await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_incoming(msg: IncomingMsg) {
|
||||
if let Ok(txt) = core::str::from_utf8(&msg.payload) {
|
||||
info!("MAIN RX [{}]: {}", msg.topic.as_str(), txt);
|
||||
info!("Received MQTT message -> topic: '{}', payload: '{}'", msg.topic.as_str(), txt);
|
||||
} else {
|
||||
info!("MAIN RX [{}]: {:?}", msg.topic.as_str(), msg.payload);
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn connection(mut controller: WifiController<'static>) {
|
||||
info!("start connection task");
|
||||
info!("Device capabilities: {:?}", controller.capabilities());
|
||||
loop {
|
||||
match esp_wifi::wifi::wifi_state() {
|
||||
WifiState::StaConnected => {
|
||||
controller.wait_for_event(WifiEvent::StaDisconnected).await;
|
||||
Timer::after(Duration::from_millis(5000)).await
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if !matches!(controller.is_started(), Ok(true)) {
|
||||
let client_config = Configuration::Client(ClientConfiguration {
|
||||
ssid: SSID.into(),
|
||||
password: PASSWORD.into(),
|
||||
..Default::default()
|
||||
});
|
||||
controller.set_configuration(&client_config).unwrap();
|
||||
info!("Starting wifi");
|
||||
controller.start_async().await.unwrap();
|
||||
info!("Wifi started!");
|
||||
}
|
||||
info!("About to connect...");
|
||||
|
||||
match controller.connect_async().await {
|
||||
Ok(_) => info!("Wifi connected!"),
|
||||
Err(e) => {
|
||||
info!("Failed to connect to wifi: {e:?}");
|
||||
Timer::after(Duration::from_millis(5000)).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
||||
runner.run().await
|
||||
}
|
||||
3
mqtt/src/lib.rs
Normal file
3
mqtt/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod mqtt;
|
||||
233
mqtt/src/mqtt/client.rs
Normal file
233
mqtt/src/mqtt/client.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
// src/mqtt/client.rs
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_net::{tcp::TcpSocket, Stack};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use log::info;
|
||||
use rust_mqtt::client::client::MqttClient;
|
||||
use rust_mqtt::client::client_config::{ClientConfig, MqttVersion};
|
||||
use rust_mqtt::packet::v5::publish_packet::QualityOfService;
|
||||
use rust_mqtt::packet::v5::reason_codes::ReasonCode;
|
||||
use rust_mqtt::utils::rng_generator::CountingRng;
|
||||
use static_cell::ConstStaticCell;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::channel::{Channel, Receiver};
|
||||
|
||||
use heapless::{String as HString, Vec as HVec};
|
||||
|
||||
use crate::mqtt::config::mqtt_broker_endpoint;
|
||||
|
||||
const RECONNECT_DELAY_SECS: u64 = 5;
|
||||
const KEEPALIVE_SECS: u64 = 60;
|
||||
const PING_PERIOD: Duration = Duration::from_secs(KEEPALIVE_SECS / 2);
|
||||
|
||||
// Limits for small, static buffers (no heap)
|
||||
pub const TOPIC_MAX: usize = 128;
|
||||
pub const PAYLOAD_MAX: usize = 512;
|
||||
const COMMAND_QUEUE: usize = 8;
|
||||
const EVENT_QUEUE: usize = 8;
|
||||
|
||||
// TCP socket buffers (for embassy-net TcpSocket)
|
||||
static TCP_RX_BUFFER: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
|
||||
static TCP_TX_BUFFER: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
|
||||
|
||||
// MQTT client buffers (separate from the TcpSocket's buffers)
|
||||
static MQTT_TX_BUF: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024]);
|
||||
static MQTT_RX_BUF: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024]);
|
||||
|
||||
// Tie TcpSocket lifetime to session
|
||||
type Client<'a, 'net> = MqttClient<'a, TcpSocket<'net>, 8, CountingRng>;
|
||||
|
||||
// ===================== Improved Section Starts Here ===================== //
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IncomingMsg {
|
||||
pub topic: HString<TOPIC_MAX>,
|
||||
pub payload: HVec<u8, PAYLOAD_MAX>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PublishMsg {
|
||||
topic: HString<TOPIC_MAX>,
|
||||
payload: HVec<u8, PAYLOAD_MAX>,
|
||||
qos: QualityOfService,
|
||||
retain: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Command {
|
||||
Publish(PublishMsg),
|
||||
Subscribe(HString<TOPIC_MAX>),
|
||||
}
|
||||
|
||||
static CMD_CHAN: Channel<CriticalSectionRawMutex, Command, COMMAND_QUEUE> = Channel::new();
|
||||
static EVT_CHAN: Channel<CriticalSectionRawMutex, IncomingMsg, EVENT_QUEUE> = Channel::new();
|
||||
|
||||
// --- Public API ---
|
||||
|
||||
pub async fn mqtt_publish(topic: &str, payload: &[u8], qos: QualityOfService, retain: bool) {
|
||||
CMD_CHAN
|
||||
.send(Command::Publish(PublishMsg {
|
||||
topic: truncate_str::<TOPIC_MAX>(topic),
|
||||
payload: truncate_payload(payload),
|
||||
qos,
|
||||
retain,
|
||||
}))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn mqtt_subscribe(topic: &str) {
|
||||
CMD_CHAN.send(Command::Subscribe(truncate_str::<TOPIC_MAX>(topic))).await;
|
||||
}
|
||||
|
||||
pub fn mqtt_events(
|
||||
) -> Receiver<'static, CriticalSectionRawMutex, IncomingMsg, EVENT_QUEUE> {
|
||||
EVT_CHAN.receiver()
|
||||
}
|
||||
|
||||
// --- Helper functions for memory-safe truncation ---
|
||||
fn truncate_str<const N: usize>(s: &str) -> HString<N> {
|
||||
let mut h = HString::new();
|
||||
let _ = h.push_str(&s[..s.len().min(N)]);
|
||||
h
|
||||
}
|
||||
|
||||
fn truncate_payload(data: &[u8]) -> HVec<u8, PAYLOAD_MAX> {
|
||||
let mut v = HVec::new();
|
||||
let _ = v.extend_from_slice(&data[..data.len().min(PAYLOAD_MAX)]);
|
||||
v
|
||||
}
|
||||
|
||||
// --- MQTT configuration and client setup ---
|
||||
fn build_client_config() -> ClientConfig<'static, 8, CountingRng> {
|
||||
let mut cfg = ClientConfig::new(MqttVersion::MQTTv5, CountingRng(0));
|
||||
cfg.keep_alive = KEEPALIVE_SECS as u16;
|
||||
cfg.add_client_id("esp32-client");
|
||||
cfg
|
||||
}
|
||||
|
||||
fn build_client<'a, 'net>(
|
||||
socket: TcpSocket<'net>,
|
||||
mqtt_tx: &'a mut [u8],
|
||||
mqtt_rx: &'a mut [u8],
|
||||
) -> Client<'a, 'net> {
|
||||
let mqtt_tx_len = mqtt_tx.len();
|
||||
let mqtt_rx_len = mqtt_rx.len();
|
||||
MqttClient::new(socket, mqtt_tx, mqtt_tx_len, mqtt_rx, mqtt_rx_len, build_client_config())
|
||||
}
|
||||
|
||||
// --- Connection lifecycle and main session loop ---
|
||||
|
||||
async fn connect_tcp<'net>(socket: &mut TcpSocket<'net>) -> Result<(), ()> {
|
||||
match socket.connect(mqtt_broker_endpoint()).await {
|
||||
Ok(_) => {
|
||||
info!("Connected TCP to MQTT broker");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
info!("TCP connect failed: {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_mqtt(client: &mut Client<'_, '_>) -> Result<(), ReasonCode> {
|
||||
client.connect_to_broker().await
|
||||
}
|
||||
|
||||
async fn run_loop(client: &mut Client<'_, '_>) -> Result<(), ReasonCode> {
|
||||
let default_topic = "esp32/topic";
|
||||
match client.subscribe_to_topic(default_topic).await {
|
||||
Ok(_) => info!("Subscribed to '{}'", default_topic),
|
||||
Err(e) => info!("Default subscribe failed: {:?}", e),
|
||||
};
|
||||
|
||||
loop {
|
||||
let net_or_ping = select(client.receive_message(), Timer::after(PING_PERIOD));
|
||||
|
||||
match select(CMD_CHAN.receive(), net_or_ping).await {
|
||||
Either::First(cmd) => handle_command(client, cmd).await?,
|
||||
Either::Second(Either::First(result)) => handle_incoming(result).await?,
|
||||
Either::Second(Either::Second(_)) => client.send_ping().await?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_command(client: &mut Client<'_, '_>, cmd: Command) -> Result<(), ReasonCode> {
|
||||
match cmd {
|
||||
Command::Publish(msg) => {
|
||||
client
|
||||
.send_message(msg.topic.as_str(), &msg.payload, msg.qos, msg.retain)
|
||||
.await
|
||||
}
|
||||
Command::Subscribe(topic) => {
|
||||
client.subscribe_to_topic(topic.as_str()).await?;
|
||||
info!("Subscribed to '{}'", topic);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_incoming(result: Result<(&str, &[u8]), ReasonCode>) -> Result<(), ReasonCode> {
|
||||
let (topic, payload) = result?;
|
||||
EVT_CHAN
|
||||
.send(IncomingMsg {
|
||||
topic: truncate_str::<TOPIC_MAX>(topic),
|
||||
payload: truncate_payload(payload),
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Session and reconnect control ---
|
||||
async fn run_one_session(
|
||||
stack: Stack<'static>,
|
||||
tcp_rx: &mut [u8],
|
||||
tcp_tx: &mut [u8],
|
||||
mqtt_tx: &mut [u8],
|
||||
mqtt_rx: &mut [u8],
|
||||
) -> Result<(), ()> {
|
||||
let mut socket = TcpSocket::new(stack, tcp_rx, tcp_tx);
|
||||
if connect_tcp(&mut socket).await.is_err() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut client = build_client(socket, mqtt_tx, mqtt_rx);
|
||||
match connect_mqtt(&mut client).await {
|
||||
Ok(_) => info!("MQTT CONNACK received"),
|
||||
Err(reason) => {
|
||||
info!("MQTT connect failed: {:?}", reason);
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
||||
run_loop(&mut client).await.map_err(|_| ())
|
||||
}
|
||||
|
||||
// --- Main MQTT embassy task ---
|
||||
#[embassy_executor::task]
|
||||
pub async fn mqtt_task(stack: Stack<'static>) {
|
||||
info!("MQTT task starting...");
|
||||
|
||||
let tcp_rx = TCP_RX_BUFFER.take();
|
||||
let tcp_tx = TCP_TX_BUFFER.take();
|
||||
let mqtt_tx = MQTT_TX_BUF.take();
|
||||
let mqtt_rx = MQTT_RX_BUF.take();
|
||||
|
||||
loop {
|
||||
let _ = run_one_session(
|
||||
stack,
|
||||
&mut tcp_rx[..],
|
||||
&mut tcp_tx[..],
|
||||
&mut mqtt_tx[..],
|
||||
&mut mqtt_rx[..],
|
||||
)
|
||||
.await;
|
||||
|
||||
info!(
|
||||
"Reconnecting in {}s after session end/failure",
|
||||
RECONNECT_DELAY_SECS
|
||||
);
|
||||
Timer::after(Duration::from_secs(RECONNECT_DELAY_SECS)).await;
|
||||
}
|
||||
}
|
||||
122
mqtt/src/mqtt/config.rs
Normal file
122
mqtt/src/mqtt/config.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
// src/mqtt/config.rs
|
||||
#![allow(dead_code)]
|
||||
|
||||
use embassy_net::{IpAddress, Ipv4Address, Ipv6Address};
|
||||
|
||||
// Compile-time values injected by build.rs
|
||||
const BROKER_IP: &str = env!("BROKER_IP");
|
||||
const BROKER_PORT: &str = env!("BROKER_PORT");
|
||||
|
||||
pub fn mqtt_broker_endpoint() -> (IpAddress, u16) {
|
||||
(parse_ip(BROKER_IP), parse_port(BROKER_PORT))
|
||||
}
|
||||
|
||||
fn parse_port(s: &str) -> u16 {
|
||||
let p: u16 = s
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("BROKER_PORT must be a valid u16 (1..=65535)"));
|
||||
assert!(p != 0, "BROKER_PORT cannot be 0");
|
||||
p
|
||||
}
|
||||
|
||||
fn parse_ip(s: &str) -> IpAddress {
|
||||
if s.contains(':') {
|
||||
IpAddress::Ipv6(parse_ipv6(s))
|
||||
} else {
|
||||
IpAddress::Ipv4(parse_ipv4(s))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ipv4(s: &str) -> Ipv4Address {
|
||||
let mut it = s.split('.');
|
||||
let a = parse_octet(it.next(), 1);
|
||||
let b = parse_octet(it.next(), 2);
|
||||
let c = parse_octet(it.next(), 3);
|
||||
let d = parse_octet(it.next(), 4);
|
||||
assert!(it.next().is_none(), "Too many IPv4 octets");
|
||||
Ipv4Address::new(a, b, c, d)
|
||||
}
|
||||
|
||||
fn parse_octet(part: Option<&str>, idx: usize) -> u8 {
|
||||
let p = part.unwrap_or_else(|| panic!("IPv4 missing octet {}", idx));
|
||||
let v: u16 = p
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("Invalid IPv4 octet {}: {}", idx, p));
|
||||
assert!(v <= 255, "IPv4 octet {} out of range: {}", idx, v);
|
||||
v as u8
|
||||
}
|
||||
|
||||
// Minimal IPv6 parser with '::' compression. Does not handle IPv4-embedded IPv6.
|
||||
fn parse_ipv6(s: &str) -> Ipv6Address {
|
||||
assert!(
|
||||
!s.contains('.'),
|
||||
"IPv4-embedded IPv6 like ::ffff:192.0.2.1 not supported; \
|
||||
use pure hex IPv6"
|
||||
);
|
||||
|
||||
let has_double = s.contains("::");
|
||||
let (left_s, right_s) = if has_double {
|
||||
let mut sp = s.splitn(2, "::");
|
||||
(sp.next().unwrap_or(""), sp.next().unwrap_or(""))
|
||||
} else {
|
||||
(s, "")
|
||||
};
|
||||
|
||||
let mut left = [0u16; 8];
|
||||
let mut right = [0u16; 8];
|
||||
let mut ll = 0usize;
|
||||
let mut rl = 0usize;
|
||||
|
||||
if !left_s.is_empty() {
|
||||
for part in left_s.split(':') {
|
||||
left[ll] = parse_group(part);
|
||||
ll += 1;
|
||||
assert!(ll <= 8, "Too many IPv6 groups on the left");
|
||||
}
|
||||
}
|
||||
|
||||
if !right_s.is_empty() {
|
||||
for part in right_s.split(':') {
|
||||
right[rl] = parse_group(part);
|
||||
rl += 1;
|
||||
assert!(rl <= 8, "Too many IPv6 groups on the right");
|
||||
}
|
||||
}
|
||||
|
||||
let zeros = if has_double {
|
||||
assert!(ll + rl < 8, "Invalid IPv6 '::' usage");
|
||||
8 - (ll + rl)
|
||||
} else {
|
||||
assert!(ll == 8, "IPv6 must have 8 groups without '::'");
|
||||
0
|
||||
};
|
||||
|
||||
let mut g = [0u16; 8];
|
||||
let mut idx = 0usize;
|
||||
|
||||
for i in 0..ll {
|
||||
g[idx] = left[i];
|
||||
idx += 1;
|
||||
}
|
||||
for _ in 0..zeros {
|
||||
g[idx] = 0;
|
||||
idx += 1;
|
||||
}
|
||||
for i in 0..rl {
|
||||
g[idx] = right[i];
|
||||
idx += 1;
|
||||
}
|
||||
assert!(idx == 8, "IPv6 did not resolve to 8 groups");
|
||||
|
||||
Ipv6Address::new(g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7])
|
||||
}
|
||||
|
||||
fn parse_group(part: &str) -> u16 {
|
||||
assert!(
|
||||
!part.is_empty(),
|
||||
"Empty IPv6 group (use '::' instead for compression)"
|
||||
);
|
||||
assert!(part.len() <= 4, "IPv6 group too long: {}", part);
|
||||
u16::from_str_radix(part, 16)
|
||||
.unwrap_or_else(|_| panic!("Invalid IPv6 hex group: {}", part))
|
||||
}
|
||||
4
mqtt/src/mqtt/mod.rs
Normal file
4
mqtt/src/mqtt/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
// src/mqtt/mod.rs
|
||||
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
17
mqtt_display/.cargo/config.toml
Normal file
17
mqtt_display/.cargo/config.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
4
mqtt_display/.env_temp
Normal file
4
mqtt_display/.env_temp
Normal file
@@ -0,0 +1,4 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
BROKER_IP= "5.196.78.28"
|
||||
BROKER_PORT= "1883"
|
||||
20
mqtt_display/.gitignore
vendored
Normal file
20
mqtt_display/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1985
mqtt_display/Cargo.lock
generated
Normal file
1985
mqtt_display/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
93
mqtt_display/Cargo.toml
Normal file
93
mqtt_display/Cargo.toml
Normal file
@@ -0,0 +1,93 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "projekt_final"
|
||||
rust-version = "1.89.0"
|
||||
version = "0.2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "projekt_final"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
"esp32",
|
||||
"log-04",
|
||||
"unstable",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
|
||||
embassy-net = { version = "0.7.0", features = [
|
||||
"dhcpv4",
|
||||
"proto-ipv6",
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"tcp",
|
||||
"udp",
|
||||
] }
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.5.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
esp-wifi = { version = "0.15.0", features = [
|
||||
"builtin-scheduler",
|
||||
"esp-alloc",
|
||||
"esp32",
|
||||
"log-04",
|
||||
"smoltcp",
|
||||
"wifi",
|
||||
] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"proto-ipv6",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
rust-mqtt = { version = "0.3.0", default-features = false, features = ["no_std"] }
|
||||
defmt-rtt = "1.0.0"
|
||||
embassy-futures = "0.1.2"
|
||||
embassy-sync = "0.7.2"
|
||||
heapless = "0.9.1"
|
||||
mousefood = { git = "https://github.com/j-g00da/mousefood", branch = "main", default-features = false }
|
||||
ssd1306 = "0.10.0"
|
||||
ratatui = { version = "0.30.0", default-features = false, features = ["macros", "all-widgets", "portable-atomic"] }
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
12
mqtt_display/Makefile
Normal file
12
mqtt_display/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
|
||||
info:
|
||||
espflash board-info
|
||||
73
mqtt_display/build.rs
Normal file
73
mqtt_display/build.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
fn main() {
|
||||
// Explicitly load .env from the project root, even when called from other dirs
|
||||
let project_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let dotenv_path = std::path::Path::new(&project_root).join(".env");
|
||||
if dotenvy::from_path(dotenv_path.clone()).is_ok() {
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
// Pass WIFI credentials into firmware
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
if let Ok(ip) = std::env::var("BROKER_IP") {
|
||||
println!("cargo:rustc-env=BROKER_IP={}", ip);
|
||||
}
|
||||
if let Ok(port) = std::env::var("BROKER_PORT") {
|
||||
println!("cargo:rustc-env=BROKER_PORT={}", port);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
0
mqtt_display/expanded.rs
Normal file
0
mqtt_display/expanded.rs
Normal file
2
mqtt_display/rust-toolchain.toml
Normal file
2
mqtt_display/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
186
mqtt_display/src/bin/main.rs
Normal file
186
mqtt_display/src/bin/main.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
// src/bin/main.rs
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types"
|
||||
)]
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_net::{Runner, StackResources};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_alloc as _;
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{clock::CpuClock, rng::Rng, timer::timg::TimerGroup};
|
||||
use esp_wifi::{
|
||||
init,
|
||||
wifi::{ClientConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiState},
|
||||
EspWifiController,
|
||||
};
|
||||
use log::info;
|
||||
use rust_mqtt::packet::v5::publish_packet::QualityOfService;
|
||||
use projekt_final::mqtt::client::{
|
||||
mqtt_events, mqtt_publish, mqtt_subscribe, mqtt_task, IncomingMsg,
|
||||
};
|
||||
use projekt_final::i2c::com::i2c_check;
|
||||
use projekt_final::i2c::com::display_task;
|
||||
use defmt_rtt as _;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
macro_rules! mk_static {
|
||||
($t:ty,$val:expr) => {{
|
||||
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||||
#[deny(unused_attributes)]
|
||||
let x = STATIC_CELL.uninit().write(($val));
|
||||
x
|
||||
}};
|
||||
}
|
||||
|
||||
const SSID: &str = env!("SSID");
|
||||
const PASSWORD: &str = env!("PASSWORD");
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 72 * 1024);
|
||||
|
||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
let mut rng = Rng::new(peripherals.RNG);
|
||||
|
||||
let esp_wifi_ctrl = &*mk_static!(
|
||||
EspWifiController<'static>,
|
||||
init(timg0.timer0, rng.clone()).unwrap()
|
||||
);
|
||||
|
||||
let (controller, interfaces) =
|
||||
esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).unwrap();
|
||||
|
||||
let wifi_interface = interfaces.sta;
|
||||
|
||||
let timg1 = TimerGroup::new(peripherals.TIMG1);
|
||||
esp_hal_embassy::init(timg1.timer0);
|
||||
|
||||
let config = embassy_net::Config::dhcpv4(Default::default());
|
||||
|
||||
let seed = (rng.random() as u64) << 32 | rng.random() as u64;
|
||||
|
||||
// Init network stack
|
||||
let (stack, runner) = embassy_net::new(
|
||||
wifi_interface,
|
||||
config,
|
||||
mk_static!(StackResources<3>, StackResources::<3>::new()),
|
||||
seed,
|
||||
);
|
||||
|
||||
spawner.spawn(connection(controller)).ok();
|
||||
spawner.spawn(net_task(runner)).ok();
|
||||
|
||||
// Wait for link up
|
||||
loop {
|
||||
if stack.is_link_up() {
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
info!("Waiting to get IP address...");
|
||||
loop {
|
||||
if let Some(config) = stack.config_v4() {
|
||||
info!("Got IP: {}", config.address);
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
spawner.spawn(mqtt_task(stack)).expect("failed to spawn MQTT task");
|
||||
info!("MQTT task started");
|
||||
|
||||
spawner.spawn(display_task()).expect("failed to spawn Display task");
|
||||
info!("I2C scan task started");
|
||||
|
||||
mqtt_publish("esp32/topic", b"hello from ESP32 (init)", QualityOfService::QoS1, false).await;
|
||||
info!("Sent initial MQTT message");
|
||||
|
||||
mqtt_subscribe("esp32/topic").await;
|
||||
|
||||
// Get a receiver for incoming MQTT messages
|
||||
let mqtt_rx = mqtt_events();
|
||||
|
||||
loop {
|
||||
// Drive both: either process an MQTT message or publish periodically
|
||||
match select(mqtt_rx.receive(), Timer::after(Duration::from_secs(5))).await
|
||||
{
|
||||
// Received inbound MQTT message (from broker)
|
||||
Either::First(msg) => {
|
||||
handle_incoming(msg);
|
||||
}
|
||||
// Time-based example publish
|
||||
Either::Second(_) => {
|
||||
// mqtt_publish(
|
||||
// "esp32/topic",
|
||||
// b"hello from main",
|
||||
// QualityOfService::QoS1,
|
||||
// false,
|
||||
// )
|
||||
// .await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_incoming(msg: IncomingMsg) {
|
||||
if let Ok(txt) = core::str::from_utf8(&msg.payload) {
|
||||
info!("MAIN RX [{}]: {}", msg.topic.as_str(), txt);
|
||||
info!("Received MQTT message -> topic: '{}', payload: '{}'", msg.topic.as_str(), txt);
|
||||
} else {
|
||||
info!("MAIN RX [{}]: {:?}", msg.topic.as_str(), msg.payload);
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn connection(mut controller: WifiController<'static>) {
|
||||
info!("start connection task");
|
||||
info!("Device capabilities: {:?}", controller.capabilities());
|
||||
loop {
|
||||
match esp_wifi::wifi::wifi_state() {
|
||||
WifiState::StaConnected => {
|
||||
controller.wait_for_event(WifiEvent::StaDisconnected).await;
|
||||
Timer::after(Duration::from_millis(5000)).await
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if !matches!(controller.is_started(), Ok(true)) {
|
||||
let client_config = Configuration::Client(ClientConfiguration {
|
||||
ssid: SSID.into(),
|
||||
password: PASSWORD.into(),
|
||||
..Default::default()
|
||||
});
|
||||
controller.set_configuration(&client_config).unwrap();
|
||||
info!("Starting wifi");
|
||||
controller.start_async().await.unwrap();
|
||||
info!("Wifi started!");
|
||||
}
|
||||
info!("About to connect...");
|
||||
|
||||
match controller.connect_async().await {
|
||||
Ok(_) => info!("Wifi connected!"),
|
||||
Err(e) => {
|
||||
info!("Failed to connect to wifi: {e:?}");
|
||||
Timer::after(Duration::from_millis(5000)).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
||||
runner.run().await
|
||||
}
|
||||
94
mqtt_display/src/i2c/com.rs
Normal file
94
mqtt_display/src/i2c/com.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
// src/i2c/com.rs
|
||||
|
||||
use embassy_executor::task;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_hal::{
|
||||
i2c::master::{Config, I2c},
|
||||
peripherals::Peripherals,
|
||||
};
|
||||
use ssd1306::mode::BufferedGraphicsMode;
|
||||
use log::info;
|
||||
|
||||
#[task]
|
||||
pub async fn display_task() {
|
||||
use mousefood::{EmbeddedBackend, EmbeddedBackendConfig};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
let peripherals = unsafe { Peripherals::steal() };
|
||||
|
||||
let i2c = I2c::new(peripherals.I2C0, Config::default())
|
||||
.expect("Failed to initialize I2C")
|
||||
.with_sda(peripherals.GPIO21)
|
||||
.with_scl(peripherals.GPIO22);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
display.init().unwrap();
|
||||
|
||||
let config = EmbeddedBackendConfig {
|
||||
flush_callback: alloc::boxed::Box::new(|display| {
|
||||
let d: &mut Ssd1306<_, _, BufferedGraphicsMode<DisplaySize128x64>> = display;
|
||||
d.flush().unwrap();
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let backend = EmbeddedBackend::new(&mut display, config);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
let mut counter = 0;
|
||||
|
||||
loop {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
||||
.split(f.area());
|
||||
|
||||
f.render_widget(
|
||||
Block::default().title(" ESP32 Status ").borders(Borders::ALL),
|
||||
chunks[0],
|
||||
);
|
||||
|
||||
let content = alloc::format!("MQTT Active\nCounter: {}", counter);
|
||||
f.render_widget(
|
||||
Paragraph::new(content).block(
|
||||
Block::default().borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM),
|
||||
),
|
||||
chunks[1],
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
counter += 1;
|
||||
Timer::after(Duration::from_millis(1000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[task]
|
||||
pub async fn i2c_check() {
|
||||
let peripherals = unsafe { Peripherals::steal() };
|
||||
let mut i2c = I2c::new(peripherals.I2C0, Config::default())
|
||||
.expect("Failed to initialize I2C")
|
||||
.with_sda(peripherals.GPIO21)
|
||||
.with_scl(peripherals.GPIO22);
|
||||
|
||||
loop {
|
||||
info!("hehe");
|
||||
esp_println::println!("I2C bus scan start");
|
||||
for addr in 0x03..0x78 {
|
||||
if i2c.write(addr, &[]).is_ok() {
|
||||
esp_println::println!("Device found at address 0x{:02X}", addr);
|
||||
}
|
||||
}
|
||||
Timer::after(Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
3
mqtt_display/src/i2c/mod.rs
Normal file
3
mqtt_display/src/i2c/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// src/i2c/mod.rs
|
||||
|
||||
pub mod com;
|
||||
5
mqtt_display/src/lib.rs
Normal file
5
mqtt_display/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![no_std]
|
||||
extern crate alloc;
|
||||
|
||||
pub mod mqtt;
|
||||
pub mod i2c;
|
||||
230
mqtt_display/src/mqtt/client.rs
Normal file
230
mqtt_display/src/mqtt/client.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
// src/mqtt/client.rs
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_net::{tcp::TcpSocket, Stack};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use log::info;
|
||||
use rust_mqtt::client::client::MqttClient;
|
||||
use rust_mqtt::client::client_config::{ClientConfig, MqttVersion};
|
||||
use rust_mqtt::packet::v5::publish_packet::QualityOfService;
|
||||
use rust_mqtt::packet::v5::reason_codes::ReasonCode;
|
||||
use rust_mqtt::utils::rng_generator::CountingRng;
|
||||
use static_cell::ConstStaticCell;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::channel::{Channel, Receiver};
|
||||
|
||||
use heapless::{String as HString, Vec as HVec};
|
||||
|
||||
use crate::mqtt::config::mqtt_broker_endpoint;
|
||||
|
||||
const RECONNECT_DELAY_SECS: u64 = 5;
|
||||
const KEEPALIVE_SECS: u64 = 60;
|
||||
const PING_PERIOD: Duration = Duration::from_secs(KEEPALIVE_SECS / 2);
|
||||
|
||||
// Limits for small, static buffers (no heap)
|
||||
pub const TOPIC_MAX: usize = 128;
|
||||
pub const PAYLOAD_MAX: usize = 512;
|
||||
const COMMAND_QUEUE: usize = 8;
|
||||
const EVENT_QUEUE: usize = 8;
|
||||
|
||||
// TCP socket buffers (for embassy-net TcpSocket)
|
||||
static TCP_RX_BUFFER: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
|
||||
static TCP_TX_BUFFER: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
|
||||
|
||||
// MQTT client buffers (separate from the TcpSocket's buffers)
|
||||
static MQTT_TX_BUF: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024]);
|
||||
static MQTT_RX_BUF: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024]);
|
||||
|
||||
// Tie TcpSocket lifetime to session
|
||||
type Client<'a, 'net> = MqttClient<'a, TcpSocket<'net>, 8, CountingRng>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IncomingMsg {
|
||||
pub topic: HString<TOPIC_MAX>,
|
||||
pub payload: HVec<u8, PAYLOAD_MAX>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PublishMsg {
|
||||
topic: HString<TOPIC_MAX>,
|
||||
payload: HVec<u8, PAYLOAD_MAX>,
|
||||
qos: QualityOfService,
|
||||
retain: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Command {
|
||||
Publish(PublishMsg),
|
||||
Subscribe(HString<TOPIC_MAX>),
|
||||
}
|
||||
|
||||
static CMD_CHAN: Channel<CriticalSectionRawMutex, Command, COMMAND_QUEUE> = Channel::new();
|
||||
static EVT_CHAN: Channel<CriticalSectionRawMutex, IncomingMsg, EVENT_QUEUE> = Channel::new();
|
||||
|
||||
// Public API
|
||||
pub async fn mqtt_publish(topic: &str, payload: &[u8], qos: QualityOfService, retain: bool) {
|
||||
CMD_CHAN
|
||||
.send(Command::Publish(PublishMsg {
|
||||
topic: truncate_str::<TOPIC_MAX>(topic),
|
||||
payload: truncate_payload(payload),
|
||||
qos,
|
||||
retain,
|
||||
}))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn mqtt_subscribe(topic: &str) {
|
||||
CMD_CHAN.send(Command::Subscribe(truncate_str::<TOPIC_MAX>(topic))).await;
|
||||
}
|
||||
|
||||
pub fn mqtt_events(
|
||||
) -> Receiver<'static, CriticalSectionRawMutex, IncomingMsg, EVENT_QUEUE> {
|
||||
EVT_CHAN.receiver()
|
||||
}
|
||||
|
||||
// Helper functions for memory-safe truncation
|
||||
fn truncate_str<const N: usize>(s: &str) -> HString<N> {
|
||||
let mut h = HString::new();
|
||||
let _ = h.push_str(&s[..s.len().min(N)]);
|
||||
h
|
||||
}
|
||||
|
||||
fn truncate_payload(data: &[u8]) -> HVec<u8, PAYLOAD_MAX> {
|
||||
let mut v = HVec::new();
|
||||
let _ = v.extend_from_slice(&data[..data.len().min(PAYLOAD_MAX)]);
|
||||
v
|
||||
}
|
||||
|
||||
// MQTT configuration and client setup
|
||||
fn build_client_config() -> ClientConfig<'static, 8, CountingRng> {
|
||||
let mut cfg = ClientConfig::new(MqttVersion::MQTTv5, CountingRng(0));
|
||||
cfg.keep_alive = KEEPALIVE_SECS as u16;
|
||||
cfg.add_client_id("esp32-client");
|
||||
cfg
|
||||
}
|
||||
|
||||
fn build_client<'a, 'net>(
|
||||
socket: TcpSocket<'net>,
|
||||
mqtt_tx: &'a mut [u8],
|
||||
mqtt_rx: &'a mut [u8],
|
||||
) -> Client<'a, 'net> {
|
||||
let mqtt_tx_len = mqtt_tx.len();
|
||||
let mqtt_rx_len = mqtt_rx.len();
|
||||
MqttClient::new(socket, mqtt_tx, mqtt_tx_len, mqtt_rx, mqtt_rx_len, build_client_config())
|
||||
}
|
||||
|
||||
// Connection lifecycle and main session loop
|
||||
|
||||
async fn connect_tcp<'net>(socket: &mut TcpSocket<'net>) -> Result<(), ()> {
|
||||
match socket.connect(mqtt_broker_endpoint()).await {
|
||||
Ok(_) => {
|
||||
info!("Connected TCP to MQTT broker");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
info!("TCP connect failed: {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_mqtt(client: &mut Client<'_, '_>) -> Result<(), ReasonCode> {
|
||||
client.connect_to_broker().await
|
||||
}
|
||||
|
||||
async fn run_loop(client: &mut Client<'_, '_>) -> Result<(), ReasonCode> {
|
||||
let default_topic = "esp32/topic";
|
||||
match client.subscribe_to_topic(default_topic).await {
|
||||
Ok(_) => info!("Subscribed to '{}'", default_topic),
|
||||
Err(e) => info!("Default subscribe failed: {:?}", e),
|
||||
};
|
||||
|
||||
loop {
|
||||
let net_or_ping = select(client.receive_message(), Timer::after(PING_PERIOD));
|
||||
|
||||
match select(CMD_CHAN.receive(), net_or_ping).await {
|
||||
Either::First(cmd) => handle_command(client, cmd).await?,
|
||||
Either::Second(Either::First(result)) => handle_incoming(result).await?,
|
||||
Either::Second(Either::Second(_)) => client.send_ping().await?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_command(client: &mut Client<'_, '_>, cmd: Command) -> Result<(), ReasonCode> {
|
||||
match cmd {
|
||||
Command::Publish(msg) => {
|
||||
client
|
||||
.send_message(msg.topic.as_str(), &msg.payload, msg.qos, msg.retain)
|
||||
.await
|
||||
}
|
||||
Command::Subscribe(topic) => {
|
||||
client.subscribe_to_topic(topic.as_str()).await?;
|
||||
info!("Subscribed to '{}'", topic);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_incoming(result: Result<(&str, &[u8]), ReasonCode>) -> Result<(), ReasonCode> {
|
||||
let (topic, payload) = result?;
|
||||
EVT_CHAN
|
||||
.send(IncomingMsg {
|
||||
topic: truncate_str::<TOPIC_MAX>(topic),
|
||||
payload: truncate_payload(payload),
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Session and reconnect control
|
||||
async fn run_one_session(
|
||||
stack: Stack<'static>,
|
||||
tcp_rx: &mut [u8],
|
||||
tcp_tx: &mut [u8],
|
||||
mqtt_tx: &mut [u8],
|
||||
mqtt_rx: &mut [u8],
|
||||
) -> Result<(), ()> {
|
||||
let mut socket = TcpSocket::new(stack, tcp_rx, tcp_tx);
|
||||
if connect_tcp(&mut socket).await.is_err() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut client = build_client(socket, mqtt_tx, mqtt_rx);
|
||||
match connect_mqtt(&mut client).await {
|
||||
Ok(_) => info!("MQTT CONNACK received"),
|
||||
Err(reason) => {
|
||||
info!("MQTT connect failed: {:?}", reason);
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
||||
run_loop(&mut client).await.map_err(|_| ())
|
||||
}
|
||||
|
||||
// Main MQTT embassy task
|
||||
#[embassy_executor::task]
|
||||
pub async fn mqtt_task(stack: Stack<'static>) {
|
||||
info!("MQTT task starting...");
|
||||
|
||||
let tcp_rx = TCP_RX_BUFFER.take();
|
||||
let tcp_tx = TCP_TX_BUFFER.take();
|
||||
let mqtt_tx = MQTT_TX_BUF.take();
|
||||
let mqtt_rx = MQTT_RX_BUF.take();
|
||||
|
||||
loop {
|
||||
let _ = run_one_session(
|
||||
stack,
|
||||
&mut tcp_rx[..],
|
||||
&mut tcp_tx[..],
|
||||
&mut mqtt_tx[..],
|
||||
&mut mqtt_rx[..],
|
||||
)
|
||||
.await;
|
||||
|
||||
info!(
|
||||
"Reconnecting in {}s after session end/failure",
|
||||
RECONNECT_DELAY_SECS
|
||||
);
|
||||
Timer::after(Duration::from_secs(RECONNECT_DELAY_SECS)).await;
|
||||
}
|
||||
}
|
||||
122
mqtt_display/src/mqtt/config.rs
Normal file
122
mqtt_display/src/mqtt/config.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
// src/mqtt/config.rs
|
||||
#![allow(dead_code)]
|
||||
|
||||
use embassy_net::{IpAddress, Ipv4Address, Ipv6Address};
|
||||
|
||||
// Compile-time values injected by build.rs
|
||||
const BROKER_IP: &str = env!("BROKER_IP");
|
||||
const BROKER_PORT: &str = env!("BROKER_PORT");
|
||||
|
||||
pub fn mqtt_broker_endpoint() -> (IpAddress, u16) {
|
||||
(parse_ip(BROKER_IP), parse_port(BROKER_PORT))
|
||||
}
|
||||
|
||||
fn parse_port(s: &str) -> u16 {
|
||||
let p: u16 = s
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("BROKER_PORT must be a valid u16 (1..=65535)"));
|
||||
assert!(p != 0, "BROKER_PORT cannot be 0");
|
||||
p
|
||||
}
|
||||
|
||||
fn parse_ip(s: &str) -> IpAddress {
|
||||
if s.contains(':') {
|
||||
IpAddress::Ipv6(parse_ipv6(s))
|
||||
} else {
|
||||
IpAddress::Ipv4(parse_ipv4(s))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ipv4(s: &str) -> Ipv4Address {
|
||||
let mut it = s.split('.');
|
||||
let a = parse_octet(it.next(), 1);
|
||||
let b = parse_octet(it.next(), 2);
|
||||
let c = parse_octet(it.next(), 3);
|
||||
let d = parse_octet(it.next(), 4);
|
||||
assert!(it.next().is_none(), "Too many IPv4 octets");
|
||||
Ipv4Address::new(a, b, c, d)
|
||||
}
|
||||
|
||||
fn parse_octet(part: Option<&str>, idx: usize) -> u8 {
|
||||
let p = part.unwrap_or_else(|| panic!("IPv4 missing octet {}", idx));
|
||||
let v: u16 = p
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("Invalid IPv4 octet {}: {}", idx, p));
|
||||
assert!(v <= 255, "IPv4 octet {} out of range: {}", idx, v);
|
||||
v as u8
|
||||
}
|
||||
|
||||
// Minimal IPv6 parser with '::' compression. Does not handle IPv4-embedded IPv6.
|
||||
fn parse_ipv6(s: &str) -> Ipv6Address {
|
||||
assert!(
|
||||
!s.contains('.'),
|
||||
"IPv4-embedded IPv6 like ::ffff:192.0.2.1 not supported; \
|
||||
use pure hex IPv6"
|
||||
);
|
||||
|
||||
let has_double = s.contains("::");
|
||||
let (left_s, right_s) = if has_double {
|
||||
let mut sp = s.splitn(2, "::");
|
||||
(sp.next().unwrap_or(""), sp.next().unwrap_or(""))
|
||||
} else {
|
||||
(s, "")
|
||||
};
|
||||
|
||||
let mut left = [0u16; 8];
|
||||
let mut right = [0u16; 8];
|
||||
let mut ll = 0usize;
|
||||
let mut rl = 0usize;
|
||||
|
||||
if !left_s.is_empty() {
|
||||
for part in left_s.split(':') {
|
||||
left[ll] = parse_group(part);
|
||||
ll += 1;
|
||||
assert!(ll <= 8, "Too many IPv6 groups on the left");
|
||||
}
|
||||
}
|
||||
|
||||
if !right_s.is_empty() {
|
||||
for part in right_s.split(':') {
|
||||
right[rl] = parse_group(part);
|
||||
rl += 1;
|
||||
assert!(rl <= 8, "Too many IPv6 groups on the right");
|
||||
}
|
||||
}
|
||||
|
||||
let zeros = if has_double {
|
||||
assert!(ll + rl < 8, "Invalid IPv6 '::' usage");
|
||||
8 - (ll + rl)
|
||||
} else {
|
||||
assert!(ll == 8, "IPv6 must have 8 groups without '::'");
|
||||
0
|
||||
};
|
||||
|
||||
let mut g = [0u16; 8];
|
||||
let mut idx = 0usize;
|
||||
|
||||
for i in 0..ll {
|
||||
g[idx] = left[i];
|
||||
idx += 1;
|
||||
}
|
||||
for _ in 0..zeros {
|
||||
g[idx] = 0;
|
||||
idx += 1;
|
||||
}
|
||||
for i in 0..rl {
|
||||
g[idx] = right[i];
|
||||
idx += 1;
|
||||
}
|
||||
assert!(idx == 8, "IPv6 did not resolve to 8 groups");
|
||||
|
||||
Ipv6Address::new(g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7])
|
||||
}
|
||||
|
||||
fn parse_group(part: &str) -> u16 {
|
||||
assert!(
|
||||
!part.is_empty(),
|
||||
"Empty IPv6 group (use '::' instead for compression)"
|
||||
);
|
||||
assert!(part.len() <= 4, "IPv6 group too long: {}", part);
|
||||
u16::from_str_radix(part, 16)
|
||||
.unwrap_or_else(|_| panic!("Invalid IPv6 hex group: {}", part))
|
||||
}
|
||||
4
mqtt_display/src/mqtt/mod.rs
Normal file
4
mqtt_display/src/mqtt/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
// src/mqtt/mod.rs
|
||||
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
@@ -43,29 +43,28 @@ async fn main(spawner: Spawner) {
|
||||
let rng = esp_hal::rng::Rng::new(peripherals.RNG);
|
||||
let timer1 = TimerGroup::new(peripherals.TIMG0);
|
||||
let wifi_init =
|
||||
esp_wifi::init(timer1.timer0, rng).expect("Failed to initialize WIFI/BLE controller");
|
||||
let (mut controller, _interfaces) = esp_wifi::wifi::new(&wifi_init, peripherals.WIFI)
|
||||
.expect("Failed to initialize WIFI controller");
|
||||
esp_wifi::init(timer1.timer0, rng).expect("Failed to initialize WIFI/BLE wifi_controller");
|
||||
let (mut wifi_controller, interfaces) = esp_wifi::wifi::new(&wifi_init, peripherals.WIFI)
|
||||
.expect("Failed to initialize WIFI wifi_controller");
|
||||
|
||||
let client_conf = Configuration::Client(ClientConfiguration {
|
||||
ssid: SSID.try_into().unwrap(),
|
||||
password: PASSWORD.try_into().unwrap(),
|
||||
..Default::default()
|
||||
});
|
||||
controller.set_configuration(&client_conf).unwrap();
|
||||
wifi_controller.set_configuration(&client_conf).unwrap();
|
||||
|
||||
info!("Starting WiFi...");
|
||||
controller.start_async().await.unwrap();
|
||||
wifi_controller.start_async().await.unwrap();
|
||||
info!("WiFi started, connecting...");
|
||||
|
||||
match controller.connect_async().await {
|
||||
match wifi_controller.connect_async().await {
|
||||
Ok(_) => info!("Connected to WiFi!"),
|
||||
Err(e) => info!("Failed to connect: {:?}", e),
|
||||
}
|
||||
|
||||
// keep task alive
|
||||
loop {
|
||||
Timer::after(Duration::from_secs(5)).await;
|
||||
Timer::after(Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-rc.0/examples/src/bin
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# MQTT Implementation Notes
|
||||
|
||||
* WiFi connection runs in a separate task with auto-reconnect functionality
|
||||
* Network stack operates in a dedicated packet processing task
|
||||
* MQTT uses TCP socket connection (MqttClient wraps this socket)
|
||||
* ClientConfig specifies: MQTT version, QoS levels, client ID
|
||||
* Standard flow: connect_to_broker() → send_message() or subscribe()
|
||||
|
||||
## Implementation:
|
||||
1. Perform DNS lookup for broker hostname
|
||||
2. Establish TCP socket connection
|
||||
3. Create MqttClient with configuration
|
||||
4. Implement connect, publish, and subscribe logic
|
||||
16
test_LED2/.cargo/config.toml
Normal file
16
test_LED2/.cargo/config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
test_LED2/.env_temp
Normal file
2
test_LED2/.env_temp
Normal file
@@ -0,0 +1,2 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
20
test_LED2/.gitignore
vendored
Normal file
20
test_LED2/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1343
test_LED2/Cargo.lock
generated
Normal file
1343
test_LED2/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
68
test_LED2/Cargo.toml
Normal file
68
test_LED2/Cargo.toml
Normal file
@@ -0,0 +1,68 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "test1"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "test1"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
"esp32",
|
||||
"log-04",
|
||||
"unstable",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.4.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
heapless = "0.9.1"
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
9
test_LED2/Makefile
Normal file
9
test_LED2/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
65
test_LED2/build.rs
Normal file
65
test_LED2/build.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
fn main() {
|
||||
// load .env and pass SSID/PASSWORD to compiler
|
||||
if let Ok(dotenv_path) = dotenvy::dotenv() {
|
||||
// Only rebuild if .env changes
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
test_LED2/rust-toolchain.toml
Normal file
2
test_LED2/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
49
test_LED2/src/bin/main.rs
Normal file
49
test_LED2/src/bin/main.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::gpio::{Output, Level, OutputConfig};
|
||||
use esp_hal::clock::CpuClock;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use log::info;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||||
|
||||
let timer0 = TimerGroup::new(peripherals.TIMG1);
|
||||
esp_hal_embassy::init(timer0.timer0);
|
||||
|
||||
// Initialize GPIO4 as output (starts LOW)
|
||||
let mut gpio4 = Output::new(peripherals.GPIO4, Level::Low, OutputConfig::default());
|
||||
|
||||
info!("Embassy initialized!");
|
||||
loop {
|
||||
gpio4.set_high();
|
||||
info!("GPIO4 ON");
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
|
||||
gpio4.set_low();
|
||||
info!("GPIO4 OFF");
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-rc.0/examples/src/bin
|
||||
}
|
||||
1
test_LED2/src/lib.rs
Normal file
1
test_LED2/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
#![no_std]
|
||||
16
test_LED3_timer/.cargo/config.toml
Normal file
16
test_LED3_timer/.cargo/config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
test_LED3_timer/.env_temp
Normal file
2
test_LED3_timer/.env_temp
Normal file
@@ -0,0 +1,2 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
20
test_LED3_timer/.gitignore
vendored
Normal file
20
test_LED3_timer/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1343
test_LED3_timer/Cargo.lock
generated
Normal file
1343
test_LED3_timer/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
68
test_LED3_timer/Cargo.toml
Normal file
68
test_LED3_timer/Cargo.toml
Normal file
@@ -0,0 +1,68 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "test1"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "test1"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
"esp32",
|
||||
"log-04",
|
||||
"unstable",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.4.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
heapless = "0.9.1"
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
12
test_LED3_timer/Makefile
Normal file
12
test_LED3_timer/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
|
||||
clean:
|
||||
cargo espflash erase-flash
|
||||
65
test_LED3_timer/build.rs
Normal file
65
test_LED3_timer/build.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
fn main() {
|
||||
// load .env and pass SSID/PASSWORD to compiler
|
||||
if let Ok(dotenv_path) = dotenvy::dotenv() {
|
||||
// Only rebuild if .env changes
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
test_LED3_timer/rust-toolchain.toml
Normal file
2
test_LED3_timer/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
66
test_LED3_timer/src/bin/main.rs
Normal file
66
test_LED3_timer/src/bin/main.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
use embassy_executor::Spawner;
|
||||
// use embassy_time::{Duration, Timer};
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{
|
||||
clock::CpuClock,
|
||||
gpio::{Level, Output, OutputConfig},
|
||||
timer::Timer,
|
||||
timer::timg::TimerGroup,
|
||||
time::Duration,
|
||||
};
|
||||
use log::info;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||||
|
||||
let timer_group1 = TimerGroup::new(peripherals.TIMG1);
|
||||
let timer0 = timer_group1.timer0;
|
||||
|
||||
// Initialize GPIO4 as output (starts LOW)
|
||||
let mut gpio4 = Output::new(peripherals.GPIO4, Level::Low, OutputConfig::default());
|
||||
|
||||
info!("Embassy initialized!");
|
||||
loop {
|
||||
gpio4.set_high();
|
||||
info!("GPIO4 ON");
|
||||
|
||||
// Start timer for 1 second and block until done
|
||||
let duration = Duration::from_secs(1);
|
||||
timer0.load_value(duration).unwrap();
|
||||
timer0.start();
|
||||
info!("{:?}", duration);
|
||||
|
||||
// Wait until timer completes
|
||||
while !timer0.is_interrupt_set() {}
|
||||
timer0.clear_interrupt();
|
||||
|
||||
gpio4.set_low();
|
||||
info!("GPIO4 OFF");
|
||||
|
||||
// Start timer for 1 second and block until done
|
||||
timer0.load_value(duration).unwrap();
|
||||
timer0.start();
|
||||
|
||||
while !timer0.is_interrupt_set() {}
|
||||
timer0.clear_interrupt();
|
||||
}
|
||||
}
|
||||
1
test_LED3_timer/src/lib.rs
Normal file
1
test_LED3_timer/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
#![no_std]
|
||||
16
test_LED4_soft_interrupt/.cargo/config.toml
Normal file
16
test_LED4_soft_interrupt/.cargo/config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
test_LED4_soft_interrupt/.env_temp
Normal file
2
test_LED4_soft_interrupt/.env_temp
Normal file
@@ -0,0 +1,2 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
20
test_LED4_soft_interrupt/.gitignore
vendored
Normal file
20
test_LED4_soft_interrupt/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1343
test_LED4_soft_interrupt/Cargo.lock
generated
Normal file
1343
test_LED4_soft_interrupt/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
68
test_LED4_soft_interrupt/Cargo.toml
Normal file
68
test_LED4_soft_interrupt/Cargo.toml
Normal file
@@ -0,0 +1,68 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "test1"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "test1"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
"esp32",
|
||||
"log-04",
|
||||
"unstable",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.4.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
heapless = "0.9.1"
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
12
test_LED4_soft_interrupt/Makefile
Normal file
12
test_LED4_soft_interrupt/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
|
||||
clean:
|
||||
cargo espflash erase-flash
|
||||
65
test_LED4_soft_interrupt/build.rs
Normal file
65
test_LED4_soft_interrupt/build.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
fn main() {
|
||||
// load .env and pass SSID/PASSWORD to compiler
|
||||
if let Ok(dotenv_path) = dotenvy::dotenv() {
|
||||
// Only rebuild if .env changes
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
test_LED4_soft_interrupt/rust-toolchain.toml
Normal file
2
test_LED4_soft_interrupt/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
55
test_LED4_soft_interrupt/src/bin/main.rs
Normal file
55
test_LED4_soft_interrupt/src/bin/main.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{
|
||||
clock::CpuClock,
|
||||
gpio::{Level, Output, OutputConfig},
|
||||
timer::timg::TimerGroup,
|
||||
};
|
||||
use log::info;
|
||||
extern crate alloc;
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn led_blink_task(mut led: Output<'static>) {
|
||||
loop {
|
||||
led.toggle();
|
||||
info!("LED toggled");
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||||
|
||||
let timer_group1 = TimerGroup::new(peripherals.TIMG1);
|
||||
esp_hal_embassy::init(timer_group1.timer0);
|
||||
|
||||
let gpio4 = Output::new(peripherals.GPIO4, Level::Low, OutputConfig::default());
|
||||
|
||||
info!("Embassy initialized!");
|
||||
|
||||
// Spawn LED blink task - runs independently
|
||||
spawner.spawn(led_blink_task(gpio4)).unwrap();
|
||||
|
||||
// CPU does other work here
|
||||
let mut counter: u8 = 230;
|
||||
loop {
|
||||
info!("CPU doing other work: {}", counter);
|
||||
counter = counter.wrapping_add(1);
|
||||
Timer::after(Duration::from_millis(300)).await;
|
||||
}
|
||||
}
|
||||
1
test_LED4_soft_interrupt/src/lib.rs
Normal file
1
test_LED4_soft_interrupt/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
#![no_std]
|
||||
16
test_LED5_hard_interrupt/.cargo/config.toml
Normal file
16
test_LED5_hard_interrupt/.cargo/config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
test_LED5_hard_interrupt/.env_temp
Normal file
2
test_LED5_hard_interrupt/.env_temp
Normal file
@@ -0,0 +1,2 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
20
test_LED5_hard_interrupt/.gitignore
vendored
Normal file
20
test_LED5_hard_interrupt/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1343
test_LED5_hard_interrupt/Cargo.lock
generated
Normal file
1343
test_LED5_hard_interrupt/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
64
test_LED5_hard_interrupt/Cargo.toml
Normal file
64
test_LED5_hard_interrupt/Cargo.toml
Normal file
@@ -0,0 +1,64 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "test1"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "test1"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = ["esp32", "log-04", "rt", "unstable"] }
|
||||
log = "0.4.27"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.4.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
heapless = "0.9.1"
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
12
test_LED5_hard_interrupt/Makefile
Normal file
12
test_LED5_hard_interrupt/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
|
||||
clean:
|
||||
cargo espflash erase-flash
|
||||
65
test_LED5_hard_interrupt/build.rs
Normal file
65
test_LED5_hard_interrupt/build.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
fn main() {
|
||||
// load .env and pass SSID/PASSWORD to compiler
|
||||
if let Ok(dotenv_path) = dotenvy::dotenv() {
|
||||
// Only rebuild if .env changes
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
test_LED5_hard_interrupt/rust-toolchain.toml
Normal file
2
test_LED5_hard_interrupt/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
87
test_LED5_hard_interrupt/src/bin/main.rs
Normal file
87
test_LED5_hard_interrupt/src/bin/main.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
use embassy_executor::Spawner;
|
||||
// use embassy_time::{Duration, Timer};
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{
|
||||
clock::CpuClock,
|
||||
gpio::{Level, Output, OutputConfig},
|
||||
timer::Timer,
|
||||
interrupt,
|
||||
peripherals,
|
||||
timer::timg::TimerGroup,
|
||||
time::Duration,
|
||||
};
|
||||
use core::cell::RefCell;
|
||||
use critical_section::Mutex;
|
||||
use esp_hal::handler;
|
||||
use log::info;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
static mut INTERRUPT_FIRED: bool = false;
|
||||
static TIMER0: Mutex<RefCell<Option<esp_hal::timer::timg::Timer<'static>>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||||
|
||||
let timer_group1 = TimerGroup::new(peripherals.TIMG1);
|
||||
let timer0 = timer_group1.timer0;
|
||||
|
||||
timer0.set_interrupt_handler(TG1_T0_LEVEL);
|
||||
timer0.clear_interrupt();
|
||||
timer0.load_value(Duration::from_secs(1)).unwrap();
|
||||
timer0.enable_interrupt(true);
|
||||
timer0.enable_auto_reload(true);
|
||||
timer0.start();
|
||||
|
||||
// Store timer in the static safely
|
||||
critical_section::with(|cs| {
|
||||
TIMER0.borrow_ref_mut(cs).replace(timer0);
|
||||
});
|
||||
|
||||
// Initialize GPIO4 as output (starts LOW)
|
||||
let mut gpio4 = Output::new(peripherals.GPIO4, Level::Low, OutputConfig::default());
|
||||
|
||||
info!("Embassy initialized!");
|
||||
loop {
|
||||
|
||||
info!("info!");
|
||||
// let mut counter = 0;
|
||||
// while counter < 10000000 {
|
||||
// counter += 1;
|
||||
// }
|
||||
if unsafe { INTERRUPT_FIRED } {
|
||||
gpio4.toggle();
|
||||
info!("tick");
|
||||
unsafe { INTERRUPT_FIRED = false; }
|
||||
}
|
||||
esp_hal::delay::Delay::new().delay_millis(100);
|
||||
}
|
||||
}
|
||||
|
||||
#[handler]
|
||||
fn TG1_T0_LEVEL() {
|
||||
critical_section::with(|cs| {
|
||||
if let Some(timer) = TIMER0.borrow_ref_mut(cs).as_mut() {
|
||||
|
||||
timer.clear_interrupt();
|
||||
unsafe { INTERRUPT_FIRED = true; }
|
||||
}
|
||||
});
|
||||
}
|
||||
1
test_LED5_hard_interrupt/src/lib.rs
Normal file
1
test_LED5_hard_interrupt/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
#![no_std]
|
||||
16
test_LED6_async_hard_interrupt/.cargo/config.toml
Normal file
16
test_LED6_async_hard_interrupt/.cargo/config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
test_LED6_async_hard_interrupt/.env_temp
Normal file
2
test_LED6_async_hard_interrupt/.env_temp
Normal file
@@ -0,0 +1,2 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
20
test_LED6_async_hard_interrupt/.gitignore
vendored
Normal file
20
test_LED6_async_hard_interrupt/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1350
test_LED6_async_hard_interrupt/Cargo.lock
generated
Normal file
1350
test_LED6_async_hard_interrupt/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
65
test_LED6_async_hard_interrupt/Cargo.toml
Normal file
65
test_LED6_async_hard_interrupt/Cargo.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "test1"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "test1"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = ["esp32", "log-04", "rt", "unstable"] }
|
||||
log = "0.4.27"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.4.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
heapless = "0.9.1"
|
||||
embassy-sync = "0.7.2"
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
12
test_LED6_async_hard_interrupt/Makefile
Normal file
12
test_LED6_async_hard_interrupt/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
|
||||
clean:
|
||||
cargo espflash erase-flash
|
||||
65
test_LED6_async_hard_interrupt/build.rs
Normal file
65
test_LED6_async_hard_interrupt/build.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
fn main() {
|
||||
// load .env and pass SSID/PASSWORD to compiler
|
||||
if let Ok(dotenv_path) = dotenvy::dotenv() {
|
||||
// Only rebuild if .env changes
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
test_LED6_async_hard_interrupt/rust-toolchain.toml
Normal file
2
test_LED6_async_hard_interrupt/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
89
test_LED6_async_hard_interrupt/src/bin/main.rs
Normal file
89
test_LED6_async_hard_interrupt/src/bin/main.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::signal::Signal;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_time::Duration;
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{
|
||||
clock::CpuClock,
|
||||
gpio::{Level, Output, OutputConfig},
|
||||
timer::Timer as HalTimer,
|
||||
interrupt,
|
||||
peripherals,
|
||||
timer::timg::TimerGroup,
|
||||
time::Duration as HalDuration,
|
||||
};
|
||||
use core::cell::RefCell;
|
||||
use critical_section::Mutex;
|
||||
use esp_hal::handler;
|
||||
use log::info;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
static SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||
static TIMER0: Mutex<RefCell<Option<esp_hal::timer::timg::Timer<'static>>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||||
let timer_group0 = TimerGroup::new(peripherals.TIMG0);
|
||||
esp_hal_embassy::init(timer_group0.timer0);
|
||||
|
||||
let timer_group1 = TimerGroup::new(peripherals.TIMG1);
|
||||
let timer0 = timer_group1.timer0;
|
||||
|
||||
timer0.set_interrupt_handler(TG1_T0_LEVEL);
|
||||
timer0.clear_interrupt();
|
||||
timer0.load_value(HalDuration::from_secs(1)).unwrap();
|
||||
timer0.enable_interrupt(true);
|
||||
timer0.enable_auto_reload(true);
|
||||
timer0.start();
|
||||
|
||||
// Store timer in the static safely
|
||||
critical_section::with(|cs| {
|
||||
TIMER0.borrow_ref_mut(cs).replace(timer0);
|
||||
});
|
||||
|
||||
// Initialize GPIO4 as output (starts LOW)
|
||||
let gpio4 = Output::new(peripherals.GPIO4, Level::Low, OutputConfig::default());
|
||||
|
||||
spawner.spawn(led_task(gpio4)).unwrap();
|
||||
|
||||
info!("Embassy initialized!");
|
||||
loop {
|
||||
embassy_time::Timer::after(Duration::from_secs(5)).await;
|
||||
info!("x");
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn led_task(mut gpio4: Output<'static>) {
|
||||
loop {
|
||||
SIGNAL.wait().await;
|
||||
gpio4.toggle();
|
||||
info!("tick");
|
||||
}
|
||||
}
|
||||
|
||||
#[handler]
|
||||
fn TG1_T0_LEVEL() {
|
||||
critical_section::with(|cs| {
|
||||
if let Some(timer) = TIMER0.borrow_ref_mut(cs).as_mut() {
|
||||
timer.clear_interrupt();
|
||||
SIGNAL.signal(());
|
||||
}
|
||||
});
|
||||
}
|
||||
1
test_LED6_async_hard_interrupt/src/lib.rs
Normal file
1
test_LED6_async_hard_interrupt/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
#![no_std]
|
||||
16
test_LED_pwm/.cargo/config.toml
Normal file
16
test_LED_pwm/.cargo/config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
test_LED_pwm/.env_temp
Normal file
2
test_LED_pwm/.env_temp
Normal file
@@ -0,0 +1,2 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
20
test_LED_pwm/.gitignore
vendored
Normal file
20
test_LED_pwm/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1343
test_LED_pwm/Cargo.lock
generated
Normal file
1343
test_LED_pwm/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
68
test_LED_pwm/Cargo.toml
Normal file
68
test_LED_pwm/Cargo.toml
Normal file
@@ -0,0 +1,68 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "test1"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "test1"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
"esp32",
|
||||
"log-04",
|
||||
"unstable",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-20480",
|
||||
] }
|
||||
embassy-time = { version = "0.4.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32", "log-04"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
heapless = "0.9.1"
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
9
test_LED_pwm/Makefile
Normal file
9
test_LED_pwm/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
.PHONY: all build flash
|
||||
|
||||
all: build flash
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
flash:
|
||||
cargo espflash flash --release --monitor
|
||||
65
test_LED_pwm/build.rs
Normal file
65
test_LED_pwm/build.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
fn main() {
|
||||
// load .env and pass SSID/PASSWORD to compiler
|
||||
if let Ok(dotenv_path) = dotenvy::dotenv() {
|
||||
// Only rebuild if .env changes
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
}
|
||||
|
||||
if let Ok(ssid) = std::env::var("SSID") {
|
||||
println!("cargo:rustc-env=SSID={}", ssid);
|
||||
}
|
||||
if let Ok(password) = std::env::var("PASSWORD") {
|
||||
println!("cargo:rustc-env=PASSWORD={}", password);
|
||||
}
|
||||
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
test_LED_pwm/rust-toolchain.toml
Normal file
2
test_LED_pwm/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
76
test_LED_pwm/src/bin/main.rs
Normal file
76
test_LED_pwm/src/bin/main.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::clock::CpuClock;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use esp_hal::ledc::{Ledc, LowSpeed, LSGlobalClkSource};
|
||||
use esp_hal::ledc::channel::{self, ChannelIFace};
|
||||
use esp_hal::ledc::timer::{self, TimerIFace};
|
||||
use esp_hal::time::Rate;
|
||||
use log::info;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
// generator version: 0.5.0
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||||
|
||||
let timer0 = TimerGroup::new(peripherals.TIMG1);
|
||||
esp_hal_embassy::init(timer0.timer0);
|
||||
|
||||
info!("Embassy initialized!");
|
||||
|
||||
|
||||
// LEDC initialization
|
||||
let mut ledc = Ledc::new(peripherals.LEDC);
|
||||
ledc.set_global_slow_clock(LSGlobalClkSource::APBClk);
|
||||
|
||||
let mut lstimer0 = ledc.timer::<LowSpeed>(timer::Number::Timer0);
|
||||
lstimer0.configure(timer::config::Config {
|
||||
duty: timer::config::Duty::Duty5Bit,
|
||||
clock_source: timer::LSClockSource::APBClk,
|
||||
frequency: Rate::from_khz(24),
|
||||
}).unwrap();
|
||||
|
||||
let led = peripherals.GPIO4;
|
||||
let mut channel0 = ledc.channel(channel::Number::Channel0, led);
|
||||
channel0.configure(channel::config::Config {
|
||||
timer: &lstimer0,
|
||||
duty_pct: 10,
|
||||
pin_config: channel::config::PinConfig::PushPull,
|
||||
}).unwrap();
|
||||
|
||||
loop {
|
||||
// channel0.start_duty_fade(0, 100, 1000).unwrap();
|
||||
// while channel0.is_duty_fade_running() {}
|
||||
// channel0.start_duty_fade(100, 0, 1000).unwrap();
|
||||
// while channel0.is_duty_fade_running() {}
|
||||
|
||||
channel0.set_duty(100).unwrap();
|
||||
info!("LED ON");
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
|
||||
channel0.set_duty(0).unwrap();
|
||||
info!("LED OFF");
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-rc.0/examples/src/bin
|
||||
}
|
||||
1
test_LED_pwm/src/lib.rs
Normal file
1
test_LED_pwm/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
#![no_std]
|
||||
32
test_tls_embassy/Cargo.toml
Normal file
32
test_tls_embassy/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "ping-embassy-net"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
embedded-tls = { path = "../..", features = ["alloc", "std", "log"], default-features = false }
|
||||
env_logger = "0.10"
|
||||
rand = "0.8"
|
||||
log = "0.4"
|
||||
static_cell = "1"
|
||||
embassy-executor = { version = "0.4", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] }
|
||||
embassy-time = { version = "0.2", default-features = false, features = ["std"] }
|
||||
smoltcp = { version = "0.11.0", features = ["dns-max-server-count-4"] }
|
||||
embassy-net = { version = "0.3.0", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] }
|
||||
embassy-net-tuntap = { version = "0.1.0" }
|
||||
|
||||
async-io = "1.6.0"
|
||||
futures = { version = "0.3.17" }
|
||||
libc = "0.2.101"
|
||||
clap = { version = "3.0", features = ["derive", "color"] }
|
||||
heapless = { version = "0.8", default-features = false }
|
||||
embedded-io-async = { version = "0.6" }
|
||||
embedded-io-adapters = { version = "0.6", features = ["futures-03"] }
|
||||
critical-section = { version = "1.1", features = ["std"] }
|
||||
|
||||
[patch.crates-io]
|
||||
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "51de518bd3edf15ffc76a55b6d003a633556ae56" }
|
||||
embassy-net = { git = "https://github.com/embassy-rs/embassy.git", rev = "51de518bd3edf15ffc76a55b6d003a633556ae56" }
|
||||
embassy-net-tuntap = { git = "https://github.com/embassy-rs/embassy.git", rev = "51de518bd3edf15ffc76a55b6d003a633556ae56" }
|
||||
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "51de518bd3edf15ffc76a55b6d003a633556ae56" }
|
||||
26
test_tls_embassy/README.md
Normal file
26
test_tls_embassy/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Embassy runtime example
|
||||
|
||||
This example show how you can use embedded-tls with the embassy async runtime. It will attempt to connect to an endpoint, send a "ping" message, and expect a "pong" response.
|
||||
|
||||
First, create the tap0 interface. You only need to do this once.
|
||||
|
||||
```sh
|
||||
sudo ip tuntap add name tap0 mode tap user $USER
|
||||
sudo ip link set tap0 up
|
||||
sudo ip addr add 192.168.69.100/24 dev tap0
|
||||
sudo ip -6 addr add fe80::100/64 dev tap0
|
||||
sudo ip -6 addr add fdaa::100/64 dev tap0
|
||||
sudo ip -6 route add fe80::/64 dev tap0
|
||||
sudo ip -6 route add fdaa::/64 dev tap0
|
||||
```
|
||||
|
||||
You can use the [rustls-mio](https://github.com/rustls/rustls/tree/main/examples) server example to test it as follows:
|
||||
|
||||
```sh
|
||||
# In the rustls-mio folder
|
||||
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout key.pem -out cert.pem -batch
|
||||
cargo run --bin tlsserver-mio -- -p 12345 --certs cert.pem --key key.pem --protover 1.3 --tickets --verbose echo
|
||||
|
||||
# In this folder
|
||||
RUST_LOG=trace cargo run
|
||||
```
|
||||
116
test_tls_embassy/src/main.rs
Normal file
116
test_tls_embassy/src/main.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use clap::Parser;
|
||||
use embassy_executor::{Executor, Spawner};
|
||||
use embassy_net::tcp::TcpSocket;
|
||||
use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources};
|
||||
use embassy_net_tuntap::TunTapDevice;
|
||||
use embassy_time::Duration;
|
||||
use embedded_io_async::Write;
|
||||
use embedded_tls::{Aes128GcmSha256, TlsConfig, TlsConnection, TlsContext, UnsecureProvider};
|
||||
use heapless::Vec;
|
||||
use log::*;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(version = "1.0")]
|
||||
struct Opts {
|
||||
/// TAP device name
|
||||
#[clap(long, default_value = "tap0")]
|
||||
tap: String,
|
||||
/// use a static IP instead of DHCP
|
||||
#[clap(long)]
|
||||
static_ip: bool,
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(stack: &'static Stack<TunTapDevice>) -> ! {
|
||||
stack.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn main_task(spawner: Spawner) {
|
||||
let opts: Opts = Opts::parse();
|
||||
|
||||
// Init network device
|
||||
let device = TunTapDevice::new(&opts.tap).unwrap();
|
||||
|
||||
// Choose between dhcp or static ip
|
||||
let config = if opts.static_ip {
|
||||
Config::ipv4_static(embassy_net::StaticConfigV4 {
|
||||
address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24),
|
||||
dns_servers: Vec::new(),
|
||||
gateway: Some(Ipv4Address::new(192, 168, 69, 1)),
|
||||
})
|
||||
} else {
|
||||
Config::dhcpv4(Default::default())
|
||||
};
|
||||
|
||||
// Generate random seed
|
||||
let mut seed = [0; 8];
|
||||
OsRng.fill_bytes(&mut seed);
|
||||
let seed = u64::from_le_bytes(seed);
|
||||
|
||||
// Init network stack
|
||||
static STACK: StaticCell<Stack<TunTapDevice>> = StaticCell::new();
|
||||
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
|
||||
let stack = &*STACK.init(Stack::new(
|
||||
device,
|
||||
config,
|
||||
RESOURCES.init(StackResources::<3>::new()),
|
||||
seed,
|
||||
));
|
||||
|
||||
// Launch network task
|
||||
spawner.spawn(net_task(stack)).unwrap();
|
||||
|
||||
// Then we can use it!
|
||||
let mut rx_buffer = [0; 4096];
|
||||
let mut tx_buffer = [0; 4096];
|
||||
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
|
||||
|
||||
socket.set_timeout(Some(Duration::from_secs(10)));
|
||||
|
||||
let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 12345);
|
||||
log::info!("connecting to {:?}...", remote_endpoint);
|
||||
let r = socket.connect(remote_endpoint).await;
|
||||
if let Err(e) = r {
|
||||
warn!("connect error: {:?}", e);
|
||||
return;
|
||||
}
|
||||
log::info!("TCP connected!");
|
||||
|
||||
let mut read_record_buffer = [0; 16384];
|
||||
let mut write_record_buffer = [0; 16384];
|
||||
let config = TlsConfig::new().with_server_name("example.com");
|
||||
let mut tls = TlsConnection::new(socket, &mut read_record_buffer, &mut write_record_buffer);
|
||||
|
||||
tls.open(TlsContext::new(
|
||||
&config,
|
||||
UnsecureProvider::new::<Aes128GcmSha256>(OsRng),
|
||||
))
|
||||
.await
|
||||
.expect("error establishing TLS connection");
|
||||
|
||||
tls.write_all(b"ping").await.expect("error writing data");
|
||||
tls.flush().await.expect("error flushing data");
|
||||
|
||||
let mut rx_buf = [0; 128];
|
||||
let sz = tls.read(&mut rx_buf[..]).await.expect("error reading data");
|
||||
|
||||
log::info!("Read {} bytes: {:?}", sz, &rx_buf[..sz]);
|
||||
}
|
||||
|
||||
static EXECUTOR: StaticCell<Executor> = StaticCell::new();
|
||||
|
||||
fn main() {
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.filter_module("async_io", log::LevelFilter::Info)
|
||||
.format_timestamp_nanos()
|
||||
.init();
|
||||
|
||||
let executor = EXECUTOR.init(Executor::new());
|
||||
executor.run(|spawner| {
|
||||
spawner.spawn(main_task(spawner)).unwrap();
|
||||
});
|
||||
}
|
||||
16
test_uart/.cargo/config.toml
Normal file
16
test_uart/.cargo/config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.xtensa-esp32-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-Z", "stack-protector=all",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
test_uart/.env_temp
Normal file
2
test_uart/.env_temp
Normal file
@@ -0,0 +1,2 @@
|
||||
SSID = "nazov_wifi_siete"
|
||||
PASSWORD = "heslo_od_wifi"
|
||||
20
test_uart/.gitignore
vendored
Normal file
20
test_uart/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
.env
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1343
test_uart/Cargo.lock
generated
Normal file
1343
test_uart/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user