rusty-bell/src/main.rs
2024-05-29 20:55:37 +02:00

268 lines
8.4 KiB
Rust

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_executor::Spawner;
use embassy_net::tcp::TcpSocket;
use embassy_net::{Config, Ipv4Address, Stack, StackResources};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
use esp_hal::{self, IO};
use embassy_time::{Duration, Timer};
use embedded_hal_async::digital::Wait;
use esp_backtrace as _;
use esp_hal::clock::ClockControl;
use esp_hal::gpio::{AnyPin, Input, Output, PullUp, PushPull};
use esp_hal::Rng;
use esp_hal::{embassy, peripherals::Peripherals, prelude::*, timer::TimerGroup};
use esp_println::println;
use esp_wifi::wifi::{ClientConfiguration, Configuration};
use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiStaDevice, WifiState};
use esp_wifi::{initialize, EspWifiInitFor};
use rust_mqtt::client::client::MqttClient;
use rust_mqtt::client::client_config::ClientConfig;
use rust_mqtt::packet::v5::publish_packet::QualityOfService;
use rust_mqtt::utils::rng_generator::CountingRng;
use static_cell::make_static;
const SSID: &str = env!("SSID");
const PASSWORD: &str = env!("PASSWORD");
const MQTT_USERNAME: &str = env!("MQTT_USERNAME");
const MQTT_PASSWORD: &str = env!("MQTT_PASSWORD");
const MQTT_TOPIC_NAME: &str = "home/doorbell/state";
const MQTT_AVAILABILITY_TOPIC_NAME: &str = "home/doorbell/available";
#[main]
async fn main(spawner: Spawner) {
#[cfg(feature = "log")]
esp_println::logger::init_logger(log::LevelFilter::Info);
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::max(system.clock_control).freeze();
#[cfg(target_arch = "xtensa")]
let timer = esp_hal::timer::TimerGroup::new(peripherals.TIMG1, &clocks).timer0;
#[cfg(target_arch = "riscv32")]
let timer = esp_hal::systimer::SystemTimer::new(peripherals.SYSTIMER).alarm0;
let init = initialize(
EspWifiInitFor::Wifi,
timer,
Rng::new(peripherals.RNG),
system.radio_clock_control,
&clocks,
)
.unwrap();
let wifi = peripherals.WIFI;
let (wifi_interface, controller) =
esp_wifi::wifi::new_with_mode(&init, wifi, WifiStaDevice).unwrap();
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
embassy::init(&clocks, timer_group0);
let config = Config::dhcpv4(Default::default());
// TODO: generate random
let seed = 0x50EA111DA6CC1D83;
// Init network stack
let stack = &*make_static!(Stack::new(
wifi_interface,
config,
make_static!(StackResources::<3>::new()),
seed
));
spawner.spawn(connection(controller)).ok();
spawner.spawn(net_task(stack)).ok();
loop {
if stack.is_link_up() {
break;
}
Timer::after(Duration::from_millis(500)).await;
}
println!("Waiting to get IP address...");
loop {
if let Some(config) = stack.config_v4() {
println!("Got IP: {}", config.address);
break;
}
Timer::after(Duration::from_millis(500)).await;
}
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let ring_button = io.pins.gpio12.into_pull_up_input();
let open_button = io.pins.gpio14.into_pull_up_input();
let status_led = io.pins.gpio27.into_push_pull_output();
let opener = make_static!(Mutex::new(io.pins.gpio26.into_push_pull_output().into()));
spawner.must_spawn(ring_button_task(ring_button.into(), stack));
spawner.must_spawn(open_button_task(open_button.into(), opener));
spawner.must_spawn(status_led_task(status_led.into()));
}
async fn ring(stack: &'static Stack<WifiDevice<'static, WifiStaDevice>>) -> Result<(), &str> {
let mut rx_buffer = [0; 4096];
let mut tx_buffer = [0; 4096];
Timer::after(Duration::from_millis(1_000)).await;
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
socket.set_timeout(Some(embassy_time::Duration::from_secs(10)));
let remote_endpoint = (Ipv4Address::new(192, 168, 1, 3), 1883);
println!("connecting...");
let r = socket.connect(remote_endpoint).await;
if let Err(e) = r {
println!("connect error: {:?}", e);
return Err("connect error");
}
// https://github.com/JurajSadel/esp32c3-no-std-async-mqtt-demo/blob/main/src/main.rs
let mut config = ClientConfig::new(
rust_mqtt::client::client_config::MqttVersion::MQTTv5,
CountingRng(20000),
);
config.add_username(MQTT_USERNAME);
config.add_password(MQTT_PASSWORD);
// Do we need those?
// config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS1);
// config.add_client_id("clientId-8rhWgBODCl");
// config.max_packet_size = 100;
let mut recv_buffer = [0; 80];
let mut write_buffer = [0; 80];
let mut client =
MqttClient::<_, 5, _>::new(socket, &mut write_buffer, 80, &mut recv_buffer, 80, config);
if let Err(e) = client.connect_to_broker().await {
println!("connect error: {:?}", e);
return Err("connect error");
}
// TODO: This should be done at boot
client
.send_message(
MQTT_AVAILABILITY_TOPIC_NAME,
"online".as_bytes(),
QualityOfService::QoS0,
true,
)
.await
.unwrap();
client
.send_message(
MQTT_TOPIC_NAME,
"{ \"event_type\": \"press\" }".as_bytes(),
QualityOfService::QoS0,
true,
)
.await
.unwrap();
Timer::after(Duration::from_millis(5000)).await;
Ok(())
}
#[embassy_executor::task]
async fn ring_button_task(
mut button: AnyPin<Input<PullUp>>,
stack: &'static Stack<WifiDevice<'static, WifiStaDevice>>,
) {
loop {
button.wait_for_falling_edge().await.unwrap();
Timer::after_millis(10).await;
if button.is_low().unwrap() {
println!("Bell button pressed");
ring(stack)
.await
.unwrap_or_else(|e| println!("ring error: {}", e));
}
}
}
#[embassy_executor::task]
async fn status_led_task(mut status_led: AnyPin<Output<PushPull>>) {
loop {
status_led.set_high().unwrap();
Timer::after_millis(100).await;
status_led.set_low().unwrap();
Timer::after_secs(1).await;
}
}
#[embassy_executor::task]
async fn open_button_task(
mut button: AnyPin<Input<PullUp>>,
opener: &'static Mutex<CriticalSectionRawMutex, AnyPin<Output<PushPull>>>,
) {
loop {
button.wait_for_falling_edge().await.unwrap();
Timer::after_millis(10).await;
if button.is_low().unwrap() {
println!("Open button pressed");
let spawner = Spawner::for_current_executor().await;
spawner
.spawn(open_task(opener))
.unwrap_or_else(|_| println!("Already opening"));
}
}
}
#[embassy_executor::task]
async fn open_task(opener: &'static Mutex<CriticalSectionRawMutex, AnyPin<Output<PushPull>>>) {
let mut opener = opener.lock().await;
opener.set_high().unwrap();
Timer::after_secs(1).await;
opener.set_low().unwrap();
}
#[embassy_executor::task]
async fn connection(mut controller: WifiController<'static>) {
println!("start connection task");
println!("Device capabilities: {:?}", controller.get_capabilities());
loop {
if esp_wifi::wifi::get_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.try_into().unwrap(),
password: PASSWORD.try_into().unwrap(),
..Default::default()
});
controller.set_configuration(&client_config).unwrap();
println!("Starting wifi");
controller.start().await.unwrap();
println!("Wifi started!");
}
println!("About to connect...");
match controller.connect().await {
Ok(_) => println!("Wifi connected!"),
Err(e) => {
println!("Failed to connect to wifi: {e:?}");
Timer::after(Duration::from_millis(5000)).await
}
}
}
}
#[embassy_executor::task]
async fn net_task(stack: &'static Stack<WifiDevice<'static, WifiStaDevice>>) {
stack.run().await
}