Compare commits
10 Commits
da2eb1baad
...
46a86f05b3
| Author | SHA1 | Date | |
|---|---|---|---|
| 46a86f05b3 | |||
| 5c1917fb6f | |||
| 9daff326d1 | |||
| 11ca5019b5 | |||
| 4c0f284b62 | |||
| 9c582a4de1 | |||
| 3ad6892578 | |||
| 8c9afa6128 | |||
| e30e71516e | |||
| b6517d2ee6 |
32
README.md
Normal file
32
README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
## Phone-like device using esp32
|
||||||
|
|
||||||
|
This is a hobby project of mine where I try to recreate a simple phone-like
|
||||||
|
device using an ESP32-board using Rust and some additional hardware listed
|
||||||
|
below:
|
||||||
|
- Base board: https://www.electrokit.com/en/adafruit-esp32-feather-v2
|
||||||
|
- LCD Screen: https://www.electrokit.com/en/1.3lcd-monterad-pa-kort
|
||||||
|
- 4G module: https://www.electrokit.com/en/4g-modul-monterad-pa-kort-clipper-lte-4g
|
||||||
|
- 16-button keypad: https://www.electrokit.com/en/keypad-16-buttons-4x4-matrix
|
||||||
|
- 16-pin IO expander: https://www.electrokit.com/en/16-output-i/o-expander-breakout-sx1509
|
||||||
|
|
||||||
|
Drivers for each of these peripherals are written manually.
|
||||||
|
- Driver for the LCD screen is found at [display.rs](./src/display.rs)
|
||||||
|
- Interfaces via SPI
|
||||||
|
- Driver for the 4G module is found at [at_commands.rs](./src/at_commands.rs)
|
||||||
|
- Interfaces via UART
|
||||||
|
- Driver for the IO expander is found at [sx1509.rs](./src/sx1509.rs)
|
||||||
|
- Interfaces via I2C
|
||||||
|
|
||||||
|
This project additionally uses [esp-hal](github.com/esp-rs/esp-hal) as the sole
|
||||||
|
abstraction layer between the board and the code itself. Also
|
||||||
|
[rusttype](https://gitlab.redox-os.org/redox-os/rusttype) is used for rendering
|
||||||
|
TTF-fonts, which is used to render text with the bundled OpenSans-font.
|
||||||
|
|
||||||
|
This project also utilizes [esp32 RTOS](https://docs.rs/crate/esp-rtos/latest)
|
||||||
|
to run two threads on the two seperate cores on the device. One core processes
|
||||||
|
outward-facing state control and rendering, while the other processes I/O, such
|
||||||
|
as keypad presses and communication with the 4G-module.
|
||||||
|
|
||||||
|
## Demonstration
|
||||||
|
|
||||||
|
<video controls src="./demo.mp4" alt="demonstration">
|
||||||
148
src/async_io.rs
148
src/async_io.rs
@ -1,5 +1,6 @@
|
|||||||
use core::{cell::RefCell, char, marker::PhantomData};
|
use core::{cell::RefCell, char, marker::PhantomData};
|
||||||
|
|
||||||
|
use alloc::vec;
|
||||||
use alloc::{
|
use alloc::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||||
@ -8,9 +9,8 @@ use alloc::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use critical_section::Mutex;
|
use critical_section::Mutex;
|
||||||
use esp_hal::delay::Delay;
|
|
||||||
|
|
||||||
use crate::at_commands::{ATCommand, ATParseError, ATResponseParser, SimpleATCommand};
|
use crate::at_commands::{ATCommand, ATError, ATResponseParser, SimpleATCommand};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum KeypadButton {
|
pub enum KeypadButton {
|
||||||
@ -54,16 +54,19 @@ impl Keypad {
|
|||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| {
|
||||||
let mut was_pressed = self.was_pressed.borrow_ref_mut(cs);
|
let mut was_pressed = self.was_pressed.borrow_ref_mut(cs);
|
||||||
let mut is_pressed = self.is_pressed.borrow_ref_mut(cs);
|
let mut is_pressed = self.is_pressed.borrow_ref_mut(cs);
|
||||||
let mut num_presses = self.presses.borrow_ref_mut(cs);
|
let mut num_presses = self.presses.borrow_ref(cs).clone();
|
||||||
|
|
||||||
*was_pressed = is_pressed.clone();
|
*was_pressed = is_pressed.clone();
|
||||||
*is_pressed = presses;
|
*is_pressed = presses;
|
||||||
for button in is_pressed.iter() {
|
for button in is_pressed.iter() {
|
||||||
if !was_pressed.contains(button) {
|
if !was_pressed.contains(button) {
|
||||||
let num = num_presses.get(button).copied().unwrap_or(0);
|
let num = num_presses.get(button).copied().unwrap_or(0);
|
||||||
|
log::info!("{:?}", *button);
|
||||||
num_presses.insert(*button, num + 1);
|
num_presses.insert(*button, num + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*self.presses.borrow_ref_mut(cs) = num_presses;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,22 +106,11 @@ impl Keypad {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ConstructedATCommand {
|
|
||||||
/// Single-part command
|
|
||||||
Single(String),
|
|
||||||
/// Two-part AT-command
|
|
||||||
AddInfo(String, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for ConstructedATCommand {}
|
|
||||||
unsafe impl Sync for ConstructedATCommand {}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AsyncIO {
|
pub struct AsyncIO {
|
||||||
at_command: Rc<Mutex<RefCell<Option<Box<dyn SimpleATCommand>>>>>,
|
at_command: Rc<Mutex<RefCell<Option<Box<dyn SimpleATCommand>>>>>,
|
||||||
handling_at_command: Rc<Mutex<RefCell<bool>>>,
|
handling_at_command: Rc<Mutex<RefCell<bool>>>,
|
||||||
at_response: Rc<Mutex<RefCell<Option<Vec<String>>>>>,
|
at_response: Rc<Mutex<RefCell<Option<Result<Vec<String>, ATError>>>>>,
|
||||||
pub keypad: Keypad,
|
pub keypad: Keypad,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,16 +134,19 @@ pub struct ATPromise<T: ATCommand> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ATCommand> ATPromise<T> {
|
impl<T: ATCommand> ATPromise<T> {
|
||||||
pub fn poll(&self, io: &mut AsyncIO) -> Option<Option<Result<T::Response, ATParseError>>> {
|
pub fn poll(&self, io: &mut AsyncIO) -> Option<Result<T::Response, ATError>> {
|
||||||
match io.poll_at_response() {
|
match io.poll_at_response() {
|
||||||
Some(response) => match response {
|
|
||||||
Some(response) => {
|
Some(response) => {
|
||||||
|
log::info!("Response: {:?}", response);
|
||||||
|
match response {
|
||||||
|
Ok(response) => {
|
||||||
let mut parser = ATResponseParser::from(response);
|
let mut parser = ATResponseParser::from(response);
|
||||||
Some(Some(T::parse_response(&mut parser)))
|
Some(T::parse_response(&mut parser))
|
||||||
}
|
}
|
||||||
None => Some(None),
|
Err(err) => Some(Err(err)),
|
||||||
},
|
}
|
||||||
None => Some(None),
|
}
|
||||||
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,7 +176,7 @@ impl AsyncIO {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn set_at_response(&self, response_str: Vec<String>) {
|
pub unsafe fn set_at_response(&self, response_str: Result<Vec<String>, ATError>) {
|
||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| {
|
||||||
*self.handling_at_command.borrow_ref_mut(cs) = false;
|
*self.handling_at_command.borrow_ref_mut(cs) = false;
|
||||||
let mut command = self.at_command.borrow_ref_mut(cs);
|
let mut command = self.at_command.borrow_ref_mut(cs);
|
||||||
@ -191,15 +186,15 @@ impl AsyncIO {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_at_response(&self) -> Option<Option<Vec<String>>> {
|
fn poll_at_response(&self) -> Option<Result<Vec<String>, ATError>> {
|
||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| {
|
||||||
let command = self.at_command.borrow_ref(cs);
|
let command = self.at_command.borrow_ref(cs);
|
||||||
let mut response = self.at_response.borrow_ref_mut(cs);
|
let mut response = self.at_response.borrow_ref_mut(cs);
|
||||||
if command.is_some() || *self.handling_at_command.borrow_ref(cs) {
|
if command.is_some() {
|
||||||
return Some(None);
|
return None;
|
||||||
}
|
}
|
||||||
if let Some(resp) = response.take() {
|
if let Some(resp) = response.take() {
|
||||||
return Some(Some(resp));
|
return Some(resp);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
@ -240,3 +235,104 @@ impl NumberInput {
|
|||||||
&self.written
|
&self.written
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KeypadButton {
|
||||||
|
pub fn get_chars(&self) -> Option<Vec<char>> {
|
||||||
|
match self {
|
||||||
|
KeypadButton::Keypad1 => Some(vec![' ', '1']),
|
||||||
|
KeypadButton::Keypad2 => Some(vec!['a', 'b', 'c', '2']),
|
||||||
|
KeypadButton::Keypad3 => Some(vec!['d', 'e', 'f', '3']),
|
||||||
|
KeypadButton::Keypad4 => Some(vec!['g', 'h', 'i', '4']),
|
||||||
|
KeypadButton::Keypad5 => Some(vec!['j', 'k', 'l', '5']),
|
||||||
|
KeypadButton::Keypad6 => Some(vec!['m', 'n', 'o', '6']),
|
||||||
|
KeypadButton::Keypad7 => Some(vec!['p', 'q', 'r', 's', '7']),
|
||||||
|
KeypadButton::Keypad8 => Some(vec!['t', 'u', 'v', '8']),
|
||||||
|
KeypadButton::Keypad9 => Some(vec!['w', 'x', 'y', 'z', '9']),
|
||||||
|
KeypadButton::Keypad0 => Some(vec![' ', '0']),
|
||||||
|
KeypadButton::KeypadStar => Some(vec!['*']),
|
||||||
|
KeypadButton::KeypadHash => Some(vec!['#']),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LastPressed {
|
||||||
|
key: KeypadButton,
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LastPressed {
|
||||||
|
pub fn increment(&mut self) {
|
||||||
|
let chars = self.key.get_chars();
|
||||||
|
if let Some(chars) = chars {
|
||||||
|
self.idx = (self.idx + 1) % chars.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_char(&self) -> char {
|
||||||
|
self.key.get_chars().unwrap()[self.idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TEXT_KEYS: [KeypadButton; 13] = [
|
||||||
|
KeypadButton::Keypad0,
|
||||||
|
KeypadButton::Keypad1,
|
||||||
|
KeypadButton::Keypad2,
|
||||||
|
KeypadButton::Keypad3,
|
||||||
|
KeypadButton::Keypad4,
|
||||||
|
KeypadButton::Keypad5,
|
||||||
|
KeypadButton::Keypad6,
|
||||||
|
KeypadButton::Keypad7,
|
||||||
|
KeypadButton::Keypad8,
|
||||||
|
KeypadButton::Keypad9,
|
||||||
|
KeypadButton::Keypad0,
|
||||||
|
KeypadButton::KeypadStar,
|
||||||
|
KeypadButton::KeypadHash,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct TextInput {
|
||||||
|
written: String,
|
||||||
|
last_pressed: Option<LastPressed>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextInput {
|
||||||
|
pub fn poll(&mut self, io: &mut AsyncIO) -> bool {
|
||||||
|
let mut wrote = false;
|
||||||
|
for button in io.keypad.just_pressed_buttons() {
|
||||||
|
if let Some(mut last_pressed) = self.last_pressed.take() {
|
||||||
|
if last_pressed.key == button {
|
||||||
|
last_pressed.increment();
|
||||||
|
self.last_pressed = Some(last_pressed);
|
||||||
|
wrote = true;
|
||||||
|
} else if TEXT_KEYS.contains(&button) {
|
||||||
|
self.written += &last_pressed.get_char().to_string();
|
||||||
|
self.last_pressed = Some(LastPressed {
|
||||||
|
key: button,
|
||||||
|
idx: 0,
|
||||||
|
});
|
||||||
|
wrote = true;
|
||||||
|
} else if button == KeypadButton::KeypadA {
|
||||||
|
self.written += &last_pressed.get_char().to_string();
|
||||||
|
wrote = true;
|
||||||
|
}
|
||||||
|
} else if TEXT_KEYS.contains(&button) {
|
||||||
|
self.last_pressed = Some(LastPressed {
|
||||||
|
key: button,
|
||||||
|
idx: 0,
|
||||||
|
});
|
||||||
|
wrote = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wrote
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self) -> String {
|
||||||
|
let mut written = self.written.clone();
|
||||||
|
if let Some(last_pressed) = &self.last_pressed {
|
||||||
|
written += &last_pressed.get_char().to_string();
|
||||||
|
}
|
||||||
|
written
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,10 +2,9 @@ use alloc::string::{String, ToString};
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use alloc::{borrow::ToOwned, format};
|
use alloc::{borrow::ToOwned, format};
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
use esp_hal::time::{Duration, Instant};
|
||||||
use esp_hal::{Blocking, delay::Delay, gpio::Output, uart::Uart};
|
use esp_hal::{Blocking, delay::Delay, gpio::Output, uart::Uart};
|
||||||
|
|
||||||
use crate::async_io::ConstructedATCommand;
|
|
||||||
|
|
||||||
static RESPONSES: [&'static str; 3] = ["OK", "ERROR", "DOWNLOAD"];
|
static RESPONSES: [&'static str; 3] = ["OK", "ERROR", "DOWNLOAD"];
|
||||||
|
|
||||||
pub struct ATCommands<'a, 'd> {
|
pub struct ATCommands<'a, 'd> {
|
||||||
@ -56,15 +55,19 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
self.flush_rx();
|
self.flush_rx();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn raw_command<T: SimpleATCommand>(&mut self, cmd: &T, command: String) -> Vec<String> {
|
pub fn raw_command<T: SimpleATCommand>(
|
||||||
|
&mut self,
|
||||||
|
cmd: &T,
|
||||||
|
command: String,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<Vec<String>, ATError> {
|
||||||
self.uart.flush().unwrap();
|
self.uart.flush().unwrap();
|
||||||
self.flush_rx();
|
self.flush_rx();
|
||||||
let command = command.clone() + "\r";
|
let command = command.clone() + "\r";
|
||||||
self.uart.write_str(&command).unwrap();
|
self.uart.write_str(&command).unwrap();
|
||||||
self.uart.flush().unwrap();
|
self.uart.flush().unwrap();
|
||||||
self.delay.delay_millis(500);
|
|
||||||
log::info!("Wrote command {}", command);
|
log::info!("Wrote command {}", command);
|
||||||
self.read_response(cmd, command)
|
self.read_response(cmd, command, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn raw_two_part_command<T: SimpleATCommand>(
|
pub fn raw_two_part_command<T: SimpleATCommand>(
|
||||||
@ -72,12 +75,12 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
cmd: &T,
|
cmd: &T,
|
||||||
command: String,
|
command: String,
|
||||||
additional: String,
|
additional: String,
|
||||||
) -> Vec<String> {
|
timeout: Duration,
|
||||||
|
) -> Result<Vec<String>, ATError> {
|
||||||
self.uart.flush().unwrap();
|
self.uart.flush().unwrap();
|
||||||
self.flush_rx();
|
self.flush_rx();
|
||||||
self.uart.write_str(&(command.clone() + "\r")).unwrap();
|
self.uart.write_str(&(command.clone() + "\r")).unwrap();
|
||||||
self.uart.flush().unwrap();
|
self.uart.flush().unwrap();
|
||||||
self.delay.delay_millis(500);
|
|
||||||
log::info!("Wrote command {}", command);
|
log::info!("Wrote command {}", command);
|
||||||
self.flush_rx();
|
self.flush_rx();
|
||||||
|
|
||||||
@ -92,7 +95,7 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
}
|
}
|
||||||
self.uart.flush().unwrap();
|
self.uart.flush().unwrap();
|
||||||
|
|
||||||
self.read_response(cmd, command)
|
self.read_response(cmd, command, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readline(&mut self) -> Option<String> {
|
pub fn readline(&mut self) -> Option<String> {
|
||||||
@ -104,16 +107,23 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_response<T: SimpleATCommand>(&mut self, cmd: &T, start: String) -> Vec<String> {
|
fn read_response<T: SimpleATCommand>(
|
||||||
|
&mut self,
|
||||||
|
cmd: &T,
|
||||||
|
start: String,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<Vec<String>, ATError> {
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
let mut response = None;
|
let mut response = None;
|
||||||
while response.is_none() {
|
while response.is_none() {
|
||||||
|
if (start_time + timeout) < Instant::now() {
|
||||||
|
return Err(ATError::Timeout);
|
||||||
|
}
|
||||||
self.flush_rx();
|
self.flush_rx();
|
||||||
log::info!("{:?}", self.lines);
|
|
||||||
response = self.parse_response(cmd, &start);
|
response = self.parse_response(cmd, &start);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("{:?}", response);
|
|
||||||
|
|
||||||
response.unwrap()
|
response.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +131,7 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
cmd: &T,
|
cmd: &T,
|
||||||
start: &String,
|
start: &String,
|
||||||
) -> Option<Vec<String>> {
|
) -> Option<Result<Vec<String>, ATError>> {
|
||||||
let start_idx = self
|
let start_idx = self
|
||||||
.lines
|
.lines
|
||||||
.iter()
|
.iter()
|
||||||
@ -138,6 +148,8 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
}
|
}
|
||||||
response.push(line.trim().to_string());
|
response.push(line.trim().to_string());
|
||||||
|
|
||||||
|
log::info!("{:?}", response);
|
||||||
|
|
||||||
let mut parser = ATResponseParser::from(response.clone());
|
let mut parser = ATResponseParser::from(response.clone());
|
||||||
|
|
||||||
match cmd.try_parse(&mut parser) {
|
match cmd.try_parse(&mut parser) {
|
||||||
@ -150,11 +162,11 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
for _ in 0..parser.lines {
|
for _ in 0..parser.lines {
|
||||||
output_lines.push(self.lines.remove(start_idx));
|
output_lines.push(self.lines.remove(start_idx));
|
||||||
}
|
}
|
||||||
return Some(output_lines);
|
return Some(Ok(output_lines));
|
||||||
}
|
}
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ATParseError::EOF => {}
|
ATError::EOF => {}
|
||||||
ATParseError::InvalidResponse => panic!("Invalid response"),
|
_ => return Some(Err(err)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,7 +183,9 @@ impl<'a, 'd> ATCommands<'a, 'd> {
|
|||||||
self.uart.read_ready()
|
self.uart.read_ready()
|
||||||
} {
|
} {
|
||||||
let length = self.uart.read(&mut buffer).unwrap();
|
let length = self.uart.read(&mut buffer).unwrap();
|
||||||
self.buffer += str::from_utf8(&buffer[..length]).unwrap();
|
for character in &buffer[..length] {
|
||||||
|
self.buffer += str::from_utf8(&[character.clone()]).unwrap_or("?")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let lines = self
|
let lines = self
|
||||||
@ -217,11 +231,18 @@ impl ATResponseParser {
|
|||||||
result.cloned()
|
result.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect(&mut self, text: String) -> Result<(), ATParseError> {
|
pub fn params(&mut self) -> Option<Params> {
|
||||||
|
self.readline().map(|l| Params {
|
||||||
|
raw_params: l.to_owned(),
|
||||||
|
idx: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expect(&mut self, text: String) -> Result<(), ATError> {
|
||||||
let line = if let Some(curr_line) = self.curr_line.take() {
|
let line = if let Some(curr_line) = self.curr_line.take() {
|
||||||
curr_line
|
curr_line
|
||||||
} else {
|
} else {
|
||||||
self.readline().ok_or(ATParseError::EOF)?
|
self.readline().ok_or(ATError::EOF)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(text) = line.strip_prefix(&text) {
|
if let Some(text) = line.strip_prefix(&text) {
|
||||||
@ -229,34 +250,88 @@ impl ATResponseParser {
|
|||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.curr_line = Some(line);
|
self.curr_line = Some(line);
|
||||||
Err(ATParseError::InvalidResponse)
|
Err(ATError::InvalidResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(&self) -> Option<String> {
|
||||||
|
if let Some(curr_line) = &self.curr_line {
|
||||||
|
return Some(curr_line.clone());
|
||||||
|
}
|
||||||
|
if let Some(text) = self.text.get(self.index) {
|
||||||
|
Some(text.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Params {
|
||||||
|
raw_params: String,
|
||||||
|
idx: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Params {
|
||||||
|
pub fn read_raw(&mut self) -> Option<String> {
|
||||||
|
if self.idx > 0 {
|
||||||
|
self.raw_params = self.raw_params.strip_prefix(",").unwrap().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
let returned;
|
||||||
|
|
||||||
|
if let Some(rest) = self.raw_params.strip_prefix("\"") {
|
||||||
|
let mut parts = rest.split("\"");
|
||||||
|
returned = parts.next().unwrap().to_owned();
|
||||||
|
self.raw_params = parts.collect::<Vec<_>>().join("\"")
|
||||||
|
} else {
|
||||||
|
let mut parts = self.raw_params.split(",");
|
||||||
|
returned = parts.next().unwrap().to_owned();
|
||||||
|
self.raw_params = parts.collect::<Vec<_>>().join(",")
|
||||||
|
}
|
||||||
|
Some(returned)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_u32(&mut self) -> Option<u32> {
|
||||||
|
if let Some(part) = self.read_raw() {
|
||||||
|
u32::from_str_radix(&part, 10).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_string(&mut self) -> Option<String> {
|
||||||
|
if let Some(part) = self.read_raw() {
|
||||||
|
Some(part)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum ATParseError {
|
pub enum ATError {
|
||||||
EOF,
|
EOF,
|
||||||
InvalidResponse,
|
InvalidResponse,
|
||||||
|
Timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ATCommand: Send + Sync {
|
pub trait ATCommand: Send + Sync {
|
||||||
type Response: core::fmt::Debug + ATResponse;
|
type Response: core::fmt::Debug + ATResponse;
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String>;
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError>;
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError>;
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SimpleATCommand: Send + Sync {
|
pub trait SimpleATCommand: Send + Sync {
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String>;
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError>;
|
||||||
fn try_parse(&self, parser: &mut ATResponseParser) -> Result<(), ATParseError>;
|
fn try_parse(&self, parser: &mut ATResponseParser) -> Result<(), ATError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ATCommand> SimpleATCommand for T {
|
impl<T: ATCommand> SimpleATCommand for T {
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
self.execute(at_commands)
|
self.execute(at_commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_parse(&self, parser: &mut ATResponseParser) -> Result<(), ATParseError> {
|
fn try_parse(&self, parser: &mut ATResponseParser) -> Result<(), ATError> {
|
||||||
let result = Self::parse_response(parser);
|
let result = Self::parse_response(parser);
|
||||||
result.map(|_| ())
|
result.map(|_| ())
|
||||||
}
|
}
|
||||||
@ -300,11 +375,11 @@ pub struct ATInformationCommand;
|
|||||||
impl ATCommand for ATInformationCommand {
|
impl ATCommand for ATInformationCommand {
|
||||||
type Response = ATInformation;
|
type Response = ATInformation;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_command(self, "ATI".to_string())
|
at_commands.raw_command(self, "ATI".to_string(), Duration::from_millis(2000))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
parser.expect("Manufacturer: ".to_string())?;
|
parser.expect("Manufacturer: ".to_string())?;
|
||||||
let manufacturer = parser.readline().unwrap();
|
let manufacturer = parser.readline().unwrap();
|
||||||
parser.expect("Model: ".to_string())?;
|
parser.expect("Model: ".to_string())?;
|
||||||
@ -324,7 +399,7 @@ impl ATCommand for ATInformationCommand {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,11 +424,15 @@ pub struct EnterPinCommand(pub String);
|
|||||||
impl ATCommand for EnterPinCommand {
|
impl ATCommand for EnterPinCommand {
|
||||||
type Response = EnterPinResult;
|
type Response = EnterPinResult;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_command(self, format!("AT+CPIN={}", self.0))
|
at_commands.raw_command(
|
||||||
|
self,
|
||||||
|
format!("AT+CPIN={}", self.0),
|
||||||
|
Duration::from_millis(2000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
while let Some(line) = parser.readline() {
|
while let Some(line) = parser.readline() {
|
||||||
if line.starts_with("OK") {
|
if line.starts_with("OK") {
|
||||||
return Ok(EnterPinResult::Ok);
|
return Ok(EnterPinResult::Ok);
|
||||||
@ -363,7 +442,7 @@ impl ATCommand for EnterPinCommand {
|
|||||||
return Ok(EnterPinResult::ErrorMessage(status.to_string()));
|
return Ok(EnterPinResult::ErrorMessage(status.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,11 +467,11 @@ pub struct CheckPinCommand;
|
|||||||
impl ATCommand for CheckPinCommand {
|
impl ATCommand for CheckPinCommand {
|
||||||
type Response = CheckPinResult;
|
type Response = CheckPinResult;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_command(self, "AT+CPIN?".to_string())
|
at_commands.raw_command(self, "AT+CPIN?".to_string(), Duration::from_millis(2000))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
if let Ok(_) = parser.expect("+CPIN: ".to_string()) {
|
if let Ok(_) = parser.expect("+CPIN: ".to_string()) {
|
||||||
let status = parser.readline().unwrap();
|
let status = parser.readline().unwrap();
|
||||||
while let Some(line) = parser.readline() {
|
while let Some(line) = parser.readline() {
|
||||||
@ -409,7 +488,7 @@ impl ATCommand for CheckPinCommand {
|
|||||||
return Ok(CheckPinResult::ErrorMessage(status.to_string()));
|
return Ok(CheckPinResult::ErrorMessage(status.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,11 +503,15 @@ pub struct SelectSMSFormatCommand(pub SMSFormat);
|
|||||||
impl ATCommand for SelectSMSFormatCommand {
|
impl ATCommand for SelectSMSFormatCommand {
|
||||||
type Response = SimpleATResponse;
|
type Response = SimpleATResponse;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_command(self, format!("AT+CMGF={}", self.0 as u8))
|
at_commands.raw_command(
|
||||||
|
self,
|
||||||
|
format!("AT+CMGF={}", self.0 as u8),
|
||||||
|
Duration::from_millis(2000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
while let Some(line) = parser.readline() {
|
while let Some(line) = parser.readline() {
|
||||||
if line.starts_with("OK") {
|
if line.starts_with("OK") {
|
||||||
return Ok(SimpleATResponse::Ok);
|
return Ok(SimpleATResponse::Ok);
|
||||||
@ -436,7 +519,7 @@ impl ATCommand for SelectSMSFormatCommand {
|
|||||||
return Ok(SimpleATResponse::Error);
|
return Ok(SimpleATResponse::Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,11 +543,11 @@ pub struct CheckSMSFormatCommand;
|
|||||||
impl ATCommand for CheckSMSFormatCommand {
|
impl ATCommand for CheckSMSFormatCommand {
|
||||||
type Response = CheckSMSFormatResult;
|
type Response = CheckSMSFormatResult;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_command(self, "AT+CMGF?".to_string())
|
at_commands.raw_command(self, "AT+CMGF?".to_string(), Duration::from_millis(2000))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
if let Ok(_) = parser.expect("+CMGF: ".to_string()) {
|
if let Ok(_) = parser.expect("+CMGF: ".to_string()) {
|
||||||
let mode = parser.readline().unwrap();
|
let mode = parser.readline().unwrap();
|
||||||
while let Some(line) = parser.readline() {
|
while let Some(line) = parser.readline() {
|
||||||
@ -479,7 +562,7 @@ impl ATCommand for CheckSMSFormatCommand {
|
|||||||
return Ok(CheckSMSFormatResult::Error);
|
return Ok(CheckSMSFormatResult::Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,11 +584,11 @@ pub struct ListTECharacterSetsCommand;
|
|||||||
impl ATCommand for ListTECharacterSetsCommand {
|
impl ATCommand for ListTECharacterSetsCommand {
|
||||||
type Response = TECharacterSets;
|
type Response = TECharacterSets;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_command(self, "AT+CSCS=?".to_string())
|
at_commands.raw_command(self, "AT+CSCS=?".to_string(), Duration::from_millis(2000))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
parser.expect("+CSCS: ".to_owned())?;
|
parser.expect("+CSCS: ".to_owned())?;
|
||||||
let result = parser.readline().unwrap();
|
let result = parser.readline().unwrap();
|
||||||
let result = result.strip_prefix("(").unwrap().strip_suffix(")").unwrap();
|
let result = result.strip_prefix("(").unwrap().strip_suffix(")").unwrap();
|
||||||
@ -516,7 +599,7 @@ impl ATCommand for ListTECharacterSetsCommand {
|
|||||||
return Ok(TECharacterSets::Charsets(results));
|
return Ok(TECharacterSets::Charsets(results));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,11 +627,15 @@ pub struct SetTECharsetCommand(pub TECharset);
|
|||||||
impl ATCommand for SetTECharsetCommand {
|
impl ATCommand for SetTECharsetCommand {
|
||||||
type Response = SimpleATResponse;
|
type Response = SimpleATResponse;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_command(self, format!("AT+CSCS=\"{}\"", self.0.into_str()))
|
at_commands.raw_command(
|
||||||
|
self,
|
||||||
|
format!("AT+CSCS=\"{}\"", self.0.into_str()),
|
||||||
|
Duration::from_millis(2000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
while let Some(line) = parser.readline() {
|
while let Some(line) = parser.readline() {
|
||||||
if line.starts_with("OK") {
|
if line.starts_with("OK") {
|
||||||
return Ok(SimpleATResponse::Ok);
|
return Ok(SimpleATResponse::Ok);
|
||||||
@ -556,7 +643,7 @@ impl ATCommand for SetTECharsetCommand {
|
|||||||
return Ok(SimpleATResponse::Error);
|
return Ok(SimpleATResponse::Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,15 +672,16 @@ pub struct SendSMSCommand {
|
|||||||
impl ATCommand for SendSMSCommand {
|
impl ATCommand for SendSMSCommand {
|
||||||
type Response = SendSMSResponse;
|
type Response = SendSMSResponse;
|
||||||
|
|
||||||
fn execute(&self, at_commands: &mut ATCommands) -> Vec<String> {
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
at_commands.raw_two_part_command(
|
at_commands.raw_two_part_command(
|
||||||
self,
|
self,
|
||||||
format!("AT+CMGS=\"{}\"", self.destination),
|
format!("AT+CMGS=\"{}\"", self.destination),
|
||||||
self.message.clone(),
|
self.message.clone(),
|
||||||
|
Duration::from_millis(30000),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATParseError> {
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
if let Ok(_) = parser.expect("+CMS ERROR: ".to_owned()) {
|
if let Ok(_) = parser.expect("+CMS ERROR: ".to_owned()) {
|
||||||
let result = parser.readline().unwrap();
|
let result = parser.readline().unwrap();
|
||||||
return Ok(SendSMSResponse::ErrorMessage(result));
|
return Ok(SendSMSResponse::ErrorMessage(result));
|
||||||
@ -620,6 +708,125 @@ impl ATCommand for SendSMSCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ATParseError::EOF)
|
Err(ATError::EOF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ListSMSMessagesResponse {
|
||||||
|
Ok(Vec<SMSMessageListing>),
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SMSMessageListing {
|
||||||
|
pub index: u32,
|
||||||
|
pub stat: SMSMessageStat,
|
||||||
|
pub from_addr: String,
|
||||||
|
pub to_addr: String,
|
||||||
|
pub timestamp: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ATResponse for ListSMSMessagesResponse {
|
||||||
|
fn is_error(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ListSMSMessagesResponse::Error(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SMSMessageStat {
|
||||||
|
RecUnread,
|
||||||
|
RecRead,
|
||||||
|
StoUnsent,
|
||||||
|
StoSent,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SMSMessageStat {
|
||||||
|
pub fn into_str(&self) -> String {
|
||||||
|
match self {
|
||||||
|
SMSMessageStat::RecUnread => "REC UNREAD",
|
||||||
|
SMSMessageStat::RecRead => "REC READ",
|
||||||
|
SMSMessageStat::StoUnsent => "STO UNSENT",
|
||||||
|
SMSMessageStat::StoSent => "STO SENT",
|
||||||
|
SMSMessageStat::All => "ALL",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(text: &str) -> SMSMessageStat {
|
||||||
|
match text {
|
||||||
|
"REC UNREAD" => Self::RecUnread,
|
||||||
|
"REC READ" => Self::RecRead,
|
||||||
|
"STO UNSENT" => Self::StoUnsent,
|
||||||
|
"STO SENT" => Self::StoSent,
|
||||||
|
_ => Self::All,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ListSMSMessages {
|
||||||
|
pub stat: SMSMessageStat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ATCommand for ListSMSMessages {
|
||||||
|
type Response = ListSMSMessagesResponse;
|
||||||
|
|
||||||
|
fn execute(&self, at_commands: &mut ATCommands) -> Result<Vec<String>, ATError> {
|
||||||
|
at_commands.raw_command(
|
||||||
|
self,
|
||||||
|
format!("AT+CMGL=\"{}\"", self.stat.into_str()),
|
||||||
|
Duration::from_millis(5000),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_response(parser: &mut ATResponseParser) -> Result<Self::Response, ATError> {
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
while let Ok(_) = parser.expect("+CMGL: ".to_owned()) {
|
||||||
|
let mut params = parser.params().unwrap();
|
||||||
|
let idx = params.read_u32().unwrap();
|
||||||
|
let stat = SMSMessageStat::from(¶ms.read_string().unwrap());
|
||||||
|
let to_addr = params.read_string().unwrap();
|
||||||
|
let from_addr = params.read_string().unwrap();
|
||||||
|
let timestamp = params.read_string().unwrap();
|
||||||
|
|
||||||
|
let mut message = String::new();
|
||||||
|
|
||||||
|
while let Some(line) = parser.peek() {
|
||||||
|
if line.starts_with("OK") || line.starts_with("+CMGL: ") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
message += &parser.readline().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.push(SMSMessageListing {
|
||||||
|
index: idx,
|
||||||
|
stat,
|
||||||
|
from_addr,
|
||||||
|
to_addr,
|
||||||
|
timestamp,
|
||||||
|
text: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Messages so far: {:?}", messages);
|
||||||
|
|
||||||
|
while let Some(line) = parser.readline() {
|
||||||
|
if line.starts_with("OK") {
|
||||||
|
return Ok(ListSMSMessagesResponse::Ok(messages));
|
||||||
|
}
|
||||||
|
if line.starts_with("+CMS ERROR: ") {
|
||||||
|
return Ok(ListSMSMessagesResponse::Error(
|
||||||
|
line.strip_prefix("+CMS ERROR: ").unwrap().to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ATError::EOF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/font.rs
42
src/font.rs
@ -1,10 +1,11 @@
|
|||||||
|
use alloc::string::ToString;
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
use esp_hal::DriverMode;
|
use esp_hal::DriverMode;
|
||||||
use rusttype::{Font, Point, PositionedGlyph, Scale};
|
use rusttype::{Font, Point, PositionedGlyph, Scale};
|
||||||
|
|
||||||
use alloc::vec;
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use alloc::{borrow::ToOwned, string::String};
|
use alloc::{borrow::ToOwned, string::String};
|
||||||
|
use alloc::{format, vec};
|
||||||
|
|
||||||
use crate::display::{self, Display, Position, Rgb565};
|
use crate::display::{self, Display, Position, Rgb565};
|
||||||
|
|
||||||
@ -66,9 +67,46 @@ impl<'a> FontRenderer<'a> {
|
|||||||
v_align: VerticalAlignment,
|
v_align: VerticalAlignment,
|
||||||
bg: Rgb565,
|
bg: Rgb565,
|
||||||
fg: Rgb565,
|
fg: Rgb565,
|
||||||
|
wrap_text: bool,
|
||||||
) {
|
) {
|
||||||
let text = text.into();
|
let text = text.into();
|
||||||
let rows = text.split("\n").collect::<Vec<&str>>();
|
let mut rows = text.split("\n").map(|v| v.to_string()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if wrap_text {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < rows.len() {
|
||||||
|
let row = &rows[i];
|
||||||
|
let mut parts = row.split(" ").into_iter();
|
||||||
|
|
||||||
|
let mut replaced = String::new();
|
||||||
|
let mut overflow: Option<String> = None;
|
||||||
|
replaced += parts.next().unwrap();
|
||||||
|
while let Some(part) = parts.next() {
|
||||||
|
let data = self.prepare(format!("{} {}", replaced, part));
|
||||||
|
log::info!("text: {}, width: {}", replaced, data.pixel_width);
|
||||||
|
if data.pixel_width > 240 {
|
||||||
|
overflow = Some(part.to_owned());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
replaced += &format!(" {}", part);
|
||||||
|
}
|
||||||
|
while let Some(part) = parts.next() {
|
||||||
|
if let Some(overflowed) = overflow {
|
||||||
|
overflow = Some(format!("{} {}", overflowed, part));
|
||||||
|
} else {
|
||||||
|
overflow = Some(part.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows[i] = replaced;
|
||||||
|
if let Some(overflow) = overflow {
|
||||||
|
rows.insert(i + 1, overflow);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("{:?}", rows);
|
||||||
|
|
||||||
let start_y = match v_align {
|
let start_y = match v_align {
|
||||||
VerticalAlignment::TopToBottom => position.y,
|
VerticalAlignment::TopToBottom => position.y,
|
||||||
|
|||||||
@ -155,6 +155,7 @@ fn main() -> ! {
|
|||||||
font::VerticalAlignment::Center,
|
font::VerticalAlignment::Center,
|
||||||
Rgb565::black(),
|
Rgb565::black(),
|
||||||
Rgb565::white(),
|
Rgb565::white(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
at_commands.init();
|
at_commands.init();
|
||||||
@ -256,10 +257,6 @@ fn thread_2_main(
|
|||||||
delay.delay_millis(5);
|
delay.delay_millis(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if buttons_pressed.len() > 0 {
|
|
||||||
log::info!("{:?}", buttons_pressed)
|
|
||||||
}
|
|
||||||
|
|
||||||
async_io.keypad.handle_presses(buttons_pressed);
|
async_io.keypad.handle_presses(buttons_pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
121
src/state.rs
121
src/state.rs
@ -3,7 +3,7 @@ use esp_hal::{Blocking, delay::Delay};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
async_io::{ATPromise, AsyncIO},
|
async_io::{ATPromise, AsyncIO},
|
||||||
at_commands::{ATCommand, ATParseError},
|
at_commands::{ATCommand, ATError},
|
||||||
display::{Color, Display, Position, Rgb565},
|
display::{Color, Display, Position, Rgb565},
|
||||||
font::{FontRenderer, HorizontalAlignment, VerticalAlignment},
|
font::{FontRenderer, HorizontalAlignment, VerticalAlignment},
|
||||||
};
|
};
|
||||||
@ -14,6 +14,7 @@ pub struct TextSettings {
|
|||||||
pub v_align: VerticalAlignment,
|
pub v_align: VerticalAlignment,
|
||||||
pub fg: Rgb565,
|
pub fg: Rgb565,
|
||||||
pub bg: Rgb565,
|
pub bg: Rgb565,
|
||||||
|
pub wrap: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextSettings {
|
impl Default for TextSettings {
|
||||||
@ -23,10 +24,39 @@ impl Default for TextSettings {
|
|||||||
v_align: VerticalAlignment::TopToBottom,
|
v_align: VerticalAlignment::TopToBottom,
|
||||||
fg: Rgb565::white(),
|
fg: Rgb565::white(),
|
||||||
bg: Rgb565::black(),
|
bg: Rgb565::black(),
|
||||||
|
wrap: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextSettings {
|
||||||
|
pub fn with_horizontal_align(self, align: HorizontalAlignment) -> TextSettings {
|
||||||
|
TextSettings {
|
||||||
|
h_align: align,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_vertical_align(self, align: VerticalAlignment) -> TextSettings {
|
||||||
|
TextSettings {
|
||||||
|
v_align: align,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fg(self, color: Rgb565) -> TextSettings {
|
||||||
|
TextSettings { fg: color, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_bg(self, color: Rgb565) -> TextSettings {
|
||||||
|
TextSettings { bg: color, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_wrap(self, wrap: bool) -> TextSettings {
|
||||||
|
TextSettings { wrap, ..self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub enum DrawCommands {
|
pub enum DrawCommands {
|
||||||
Clear(Color),
|
Clear(Color),
|
||||||
@ -79,6 +109,7 @@ pub struct StateManager<'a> {
|
|||||||
|
|
||||||
impl<'a> StateManager<'a> {
|
impl<'a> StateManager<'a> {
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
|
critical_section::with(|_| {
|
||||||
match self.curr_state.update(&mut self.data) {
|
match self.curr_state.update(&mut self.data) {
|
||||||
Some(next_state) => {
|
Some(next_state) => {
|
||||||
self.curr_state = next_state;
|
self.curr_state = next_state;
|
||||||
@ -87,6 +118,7 @@ impl<'a> StateManager<'a> {
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
self.data.io.keypad.clear();
|
self.data.io.keypad.clear();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
pub fn draw(&mut self) {
|
||||||
@ -106,6 +138,7 @@ impl<'a> StateManager<'a> {
|
|||||||
text_settings.v_align,
|
text_settings.v_align,
|
||||||
text_settings.bg,
|
text_settings.bg,
|
||||||
text_settings.fg,
|
text_settings.fg,
|
||||||
|
text_settings.wrap,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,7 +151,7 @@ impl<'a> StateManager<'a> {
|
|||||||
pub struct ATCommandHelper<T: ATCommand> {
|
pub struct ATCommandHelper<T: ATCommand> {
|
||||||
command: Option<T>,
|
command: Option<T>,
|
||||||
promise: Option<ATPromise<T>>,
|
promise: Option<ATPromise<T>>,
|
||||||
response: Option<Result<T::Response, ATParseError>>,
|
response: Option<Result<T::Response, ATError>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ATCommand + 'static> ATCommandHelper<T>
|
impl<T: ATCommand + 'static> ATCommandHelper<T>
|
||||||
@ -133,20 +166,17 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll(&mut self, io: &mut AsyncIO) -> Option<Result<T::Response, ATParseError>> {
|
pub fn poll(&mut self, io: &mut AsyncIO) -> Option<Result<T::Response, ATError>> {
|
||||||
if let Some(resp) = self.response.clone() {
|
if let Some(resp) = self.response.clone() {
|
||||||
return Some(resp);
|
return Some(resp);
|
||||||
} else {
|
} else {
|
||||||
if let Some(promise) = &mut self.promise {
|
if let Some(promise) = &mut self.promise {
|
||||||
match promise.poll(io) {
|
match promise.poll(io) {
|
||||||
Some(response) => match response {
|
|
||||||
Some(response) => {
|
Some(response) => {
|
||||||
self.response = Some(response);
|
self.response = Some(response);
|
||||||
self.response.clone()
|
self.response.clone()
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
}
|
||||||
} else if let Some(cmd) = self.command.take() {
|
} else if let Some(cmd) = self.command.take() {
|
||||||
self.promise = Some(io.send_at_command(cmd).unwrap());
|
self.promise = Some(io.send_at_command(cmd).unwrap());
|
||||||
@ -157,3 +187,82 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MENU_HEIGHT: usize = 240 / 30;
|
||||||
|
|
||||||
|
pub trait MenuItem {
|
||||||
|
fn as_text(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Menu<T: MenuItem> {
|
||||||
|
pub items: Vec<T>,
|
||||||
|
idx: usize,
|
||||||
|
scroll: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: MenuItem> Menu<T> {
|
||||||
|
pub fn with_item(mut self, item: T) -> Menu<T> {
|
||||||
|
self.items.push(item);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll<'s>(&'s mut self, io: &mut AsyncIO) -> Option<&'s T> {
|
||||||
|
use crate::async_io::KeypadButton;
|
||||||
|
|
||||||
|
for _ in 0..io.keypad.get_presses(KeypadButton::KeypadC) {
|
||||||
|
self.previous();
|
||||||
|
}
|
||||||
|
for _ in 0..io.keypad.get_presses(KeypadButton::KeypadD) {
|
||||||
|
self.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if io.keypad.get_presses(KeypadButton::KeypadA) > 0 {
|
||||||
|
Some(&self.items[self.idx])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
if self.idx == 0 {
|
||||||
|
// Wrap index around
|
||||||
|
self.idx = self.items.len() - 1;
|
||||||
|
if self.idx >= (self.scroll + MENU_HEIGHT) {
|
||||||
|
self.scroll = (self.idx - MENU_HEIGHT) + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.idx -= 1;
|
||||||
|
if self.idx < self.scroll {
|
||||||
|
self.scroll = self.idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
self.idx = (self.idx + 1) % self.items.len();
|
||||||
|
if self.idx == 0 {
|
||||||
|
self.scroll = 0;
|
||||||
|
} else if self.idx >= (self.scroll + MENU_HEIGHT) {
|
||||||
|
self.scroll = (self.idx - MENU_HEIGHT) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, data: &mut StateData) {
|
||||||
|
for (i, (idx, item)) in self
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.skip(self.scroll)
|
||||||
|
.take(MENU_HEIGHT)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let mut settings = TextSettings::default();
|
||||||
|
if idx == self.idx {
|
||||||
|
settings.bg = Rgb565::white();
|
||||||
|
settings.fg = Rgb565::black();
|
||||||
|
}
|
||||||
|
data.draw_text(item.as_text(), Position::new(0, (i * 30) as i16), settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
255
src/states.rs
255
src/states.rs
@ -5,19 +5,21 @@ use alloc::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
format,
|
format,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use esp_hal::time::{Duration, Instant};
|
use esp_hal::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
async_io::{ATPromise, KeypadButton, NumberInput},
|
async_io::{ATPromise, KeypadButton, NumberInput, TextInput},
|
||||||
at_commands::{
|
at_commands::{
|
||||||
ATCommand, ATInformationCommand, ATParseError, CheckPinCommand, CheckPinResult,
|
ATCommand, ATError, ATInformationCommand, CheckPinCommand, CheckPinResult, EnterPinCommand,
|
||||||
EnterPinCommand, EnterPinResult, SMSFormat, SelectSMSFormatCommand, SendSMSCommand,
|
EnterPinResult, ListSMSMessages, ListSMSMessagesResponse, SMSFormat, SMSMessageListing,
|
||||||
SendSMSResponse, SetTECharsetCommand, SimpleATResponse, TECharset,
|
SMSMessageStat, SelectSMSFormatCommand, SendSMSCommand, SendSMSResponse,
|
||||||
|
SetTECharsetCommand, SimpleATResponse, TECharset,
|
||||||
},
|
},
|
||||||
display::{Position, Rgb565},
|
display::{Position, Rgb565},
|
||||||
font::{HorizontalAlignment, VerticalAlignment},
|
font::{HorizontalAlignment, VerticalAlignment},
|
||||||
state::{ATCommandHelper, State, StateData, TextSettings},
|
state::{ATCommandHelper, Menu, MenuItem, State, StateData, TextSettings},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -53,7 +55,7 @@ impl DotsMessage {
|
|||||||
|
|
||||||
pub struct ATCommandState<Cmd: ATCommand, F>
|
pub struct ATCommandState<Cmd: ATCommand, F>
|
||||||
where
|
where
|
||||||
F: FnOnce(Result<Cmd::Response, ATParseError>) -> Box<dyn State>,
|
F: FnOnce(Result<Cmd::Response, ATError>) -> Box<dyn State>,
|
||||||
{
|
{
|
||||||
message: DotsMessage,
|
message: DotsMessage,
|
||||||
command: Cmd,
|
command: Cmd,
|
||||||
@ -63,7 +65,7 @@ where
|
|||||||
|
|
||||||
impl<Cmd: ATCommand, F> ATCommandState<Cmd, F>
|
impl<Cmd: ATCommand, F> ATCommandState<Cmd, F>
|
||||||
where
|
where
|
||||||
F: FnOnce(Result<Cmd::Response, ATParseError>) -> Box<dyn State>,
|
F: FnOnce(Result<Cmd::Response, ATError>) -> Box<dyn State>,
|
||||||
{
|
{
|
||||||
pub fn with(message: String, command: Cmd, after: F) -> ATCommandState<Cmd, F> {
|
pub fn with(message: String, command: Cmd, after: F) -> ATCommandState<Cmd, F> {
|
||||||
ATCommandState {
|
ATCommandState {
|
||||||
@ -80,7 +82,7 @@ where
|
|||||||
|
|
||||||
impl<Cmd: ATCommand + Clone + 'static, F: Clone> State for ATCommandState<Cmd, F>
|
impl<Cmd: ATCommand + Clone + 'static, F: Clone> State for ATCommandState<Cmd, F>
|
||||||
where
|
where
|
||||||
F: FnOnce(Result<Cmd::Response, ATParseError>) -> Box<dyn State>,
|
F: FnOnce(Result<Cmd::Response, ATError>) -> Box<dyn State>,
|
||||||
{
|
{
|
||||||
fn init(&mut self, data: &mut StateData) {
|
fn init(&mut self, data: &mut StateData) {
|
||||||
self.promise = Some(data.io.send_at_command(self.command.clone()).unwrap());
|
self.promise = Some(data.io.send_at_command(self.command.clone()).unwrap());
|
||||||
@ -92,13 +94,10 @@ where
|
|||||||
|
|
||||||
if let Some(promise) = &self.promise {
|
if let Some(promise) = &self.promise {
|
||||||
match promise.poll(&mut data.io) {
|
match promise.poll(&mut data.io) {
|
||||||
Some(response) => match response {
|
|
||||||
Some(response) => {
|
Some(response) => {
|
||||||
return Some((self.fun.clone())(response));
|
return Some((self.fun.clone())(response));
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
},
|
|
||||||
None => self.promise = None,
|
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -141,14 +140,23 @@ impl State for InitATState {
|
|||||||
self.message.poll();
|
self.message.poll();
|
||||||
|
|
||||||
if let Some(resp) = self.ati.poll(&mut data.io) {
|
if let Some(resp) = self.ati.poll(&mut data.io) {
|
||||||
resp.unwrap()
|
match resp {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
return Some(Box::new(TextState {
|
||||||
|
text: format!("Err: {:?}", err),
|
||||||
|
after: InitATState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.message.message = "Checking ATI".to_owned();
|
self.message.message = "Checking ATI".to_owned();
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(resp) = self.sms_charset.poll(&mut data.io) {
|
if let Some(resp) = self.sms_charset.poll(&mut data.io) {
|
||||||
match resp.unwrap() {
|
match resp {
|
||||||
|
Ok(resp) => match resp {
|
||||||
SimpleATResponse::Ok => {}
|
SimpleATResponse::Ok => {}
|
||||||
SimpleATResponse::Error => {
|
SimpleATResponse::Error => {
|
||||||
return Some(Box::new(TextState {
|
return Some(Box::new(TextState {
|
||||||
@ -156,6 +164,13 @@ impl State for InitATState {
|
|||||||
after: InitATState::default(),
|
after: InitATState::default(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
return Some(Box::new(TextState {
|
||||||
|
text: format!("Err: {:?}", err),
|
||||||
|
after: InitATState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.message.message = "Selecting SMS\ncharset".to_owned();
|
self.message.message = "Selecting SMS\ncharset".to_owned();
|
||||||
@ -163,7 +178,8 @@ impl State for InitATState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(resp) = self.te_charset.poll(&mut data.io) {
|
if let Some(resp) = self.te_charset.poll(&mut data.io) {
|
||||||
match resp.unwrap() {
|
match resp {
|
||||||
|
Ok(resp) => match resp {
|
||||||
SimpleATResponse::Ok => {}
|
SimpleATResponse::Ok => {}
|
||||||
SimpleATResponse::Error => {
|
SimpleATResponse::Error => {
|
||||||
return Some(Box::new(TextState {
|
return Some(Box::new(TextState {
|
||||||
@ -171,6 +187,13 @@ impl State for InitATState {
|
|||||||
after: InitATState::default(),
|
after: InitATState::default(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
return Some(Box::new(TextState {
|
||||||
|
text: format!("Err: {:?}", err),
|
||||||
|
after: InitATState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.message.message = "Selecting TE\ncharset".to_owned();
|
self.message.message = "Selecting TE\ncharset".to_owned();
|
||||||
@ -230,12 +253,10 @@ impl State for EnterPinState {
|
|||||||
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
|
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
|
||||||
if let Some(helper) = &mut self.helper {
|
if let Some(helper) = &mut self.helper {
|
||||||
match helper.poll(&mut data.io) {
|
match helper.poll(&mut data.io) {
|
||||||
Some(response) => match response.unwrap() {
|
Some(response) => match response {
|
||||||
|
Ok(response) => match response {
|
||||||
EnterPinResult::Ok => {
|
EnterPinResult::Ok => {
|
||||||
return Some(Box::new(TextState {
|
return Some(Box::new(MainMenuState::default()));
|
||||||
text: "SIM entered\nsuccessfully".to_owned(),
|
|
||||||
after: PhoneNumberState::default(),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
EnterPinResult::Error => {
|
EnterPinResult::Error => {
|
||||||
return Some(Box::new(TextState {
|
return Some(Box::new(TextState {
|
||||||
@ -250,6 +271,13 @@ impl State for EnterPinState {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Err(err) => {
|
||||||
|
return Some(Box::new(TextState {
|
||||||
|
text: format!("Error:\n{:?}", err),
|
||||||
|
after: EnterPinState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
None => return None,
|
None => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,6 +303,173 @@ impl State for EnterPinState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
enum MainMenuItem {
|
||||||
|
#[default]
|
||||||
|
SendSMS,
|
||||||
|
ReadSMS,
|
||||||
|
}
|
||||||
|
impl MenuItem for MainMenuItem {
|
||||||
|
fn as_text(&self) -> String {
|
||||||
|
match self {
|
||||||
|
MainMenuItem::SendSMS => "Send SMS",
|
||||||
|
MainMenuItem::ReadSMS => "Read SMS",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MainMenuState {
|
||||||
|
menu: Menu<MainMenuItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MainMenuState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
menu: Menu::default()
|
||||||
|
.with_item(MainMenuItem::SendSMS)
|
||||||
|
.with_item(MainMenuItem::ReadSMS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State for MainMenuState {
|
||||||
|
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
|
||||||
|
if let Some(option) = self.menu.poll(&mut data.io) {
|
||||||
|
match option {
|
||||||
|
MainMenuItem::SendSMS => return Some(Box::new(PhoneNumberState::default())),
|
||||||
|
MainMenuItem::ReadSMS => {
|
||||||
|
return Some(Box::new(ListSMSMessagesState::default()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, data: &mut StateData) {
|
||||||
|
data.clear_screen(Rgb565::black().as_color());
|
||||||
|
self.menu.draw(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct MessageListing {
|
||||||
|
from: String,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuItem for MessageListing {
|
||||||
|
fn as_text(&self) -> String {
|
||||||
|
format!("{}: {}", self.from, self.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct ListSMSMessagesState {
|
||||||
|
menu: Menu<MessageListing>,
|
||||||
|
promise: Option<ATPromise<ListSMSMessages>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State for ListSMSMessagesState {
|
||||||
|
fn init(&mut self, data: &mut StateData) {
|
||||||
|
self.promise = data
|
||||||
|
.io
|
||||||
|
.send_at_command(ListSMSMessages {
|
||||||
|
stat: SMSMessageStat::All,
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
|
||||||
|
if let Some(promise) = &self.promise {
|
||||||
|
match promise.poll(&mut data.io) {
|
||||||
|
Some(result) => {
|
||||||
|
match result {
|
||||||
|
Ok(response) => match response {
|
||||||
|
ListSMSMessagesResponse::Ok(messages) => {
|
||||||
|
for message in messages {
|
||||||
|
self.menu = self.menu.clone().with_item(MessageListing {
|
||||||
|
from: message.from_addr.clone(),
|
||||||
|
text: message.text.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListSMSMessagesResponse::Error(err) => {
|
||||||
|
return Some(Box::new(TextState {
|
||||||
|
text: format!("Error\n{:?}", err),
|
||||||
|
after: MainMenuState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
return Some(Box::new(TextState {
|
||||||
|
text: format!("Error\n{:?}", err),
|
||||||
|
after: MainMenuState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.promise = None;
|
||||||
|
}
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(listing) = self.menu.poll(&mut data.io) {
|
||||||
|
return Some(Box::new(ReadMessageState {
|
||||||
|
message: listing.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if data.io.keypad.get_presses(KeypadButton::KeypadB) > 0 {
|
||||||
|
Some(Box::new(MainMenuState::default()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, data: &mut StateData) {
|
||||||
|
data.clear_screen(Rgb565::black().as_color());
|
||||||
|
if self.menu.items.len() == 0 {
|
||||||
|
data.draw_text(
|
||||||
|
"Loading\nmessages",
|
||||||
|
Position::new(0, 0),
|
||||||
|
TextSettings::default(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.menu.draw(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct ReadMessageState {
|
||||||
|
message: MessageListing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State for ReadMessageState {
|
||||||
|
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
|
||||||
|
if data.io.keypad.get_presses(KeypadButton::KeypadB) > 0 {
|
||||||
|
Some(Box::new(ListSMSMessagesState::default()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, data: &mut StateData) {
|
||||||
|
data.clear_screen(Rgb565::black().as_color());
|
||||||
|
data.draw_text(
|
||||||
|
self.message.from.clone(),
|
||||||
|
Position::new(0, 0),
|
||||||
|
TextSettings::default(),
|
||||||
|
);
|
||||||
|
data.draw_text(
|
||||||
|
self.message.text.clone(),
|
||||||
|
Position::new(0, 30),
|
||||||
|
TextSettings::default().with_wrap(true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct PhoneNumberState {
|
pub struct PhoneNumberState {
|
||||||
input: NumberInput,
|
input: NumberInput,
|
||||||
@ -307,13 +502,12 @@ impl State for PhoneNumberState {
|
|||||||
|
|
||||||
pub struct MessageState {
|
pub struct MessageState {
|
||||||
number: String,
|
number: String,
|
||||||
input: NumberInput,
|
input: TextInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State for MessageState {
|
impl State for MessageState {
|
||||||
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
|
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
|
||||||
self.input.poll(&mut data.io);
|
if !self.input.poll(&mut data.io) {
|
||||||
|
|
||||||
if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 {
|
if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 {
|
||||||
Some(Box::new(ATCommandState::with(
|
Some(Box::new(ATCommandState::with(
|
||||||
"Sending SMS..".to_owned(),
|
"Sending SMS..".to_owned(),
|
||||||
@ -321,24 +515,33 @@ impl State for MessageState {
|
|||||||
destination: self.number.clone(),
|
destination: self.number.clone(),
|
||||||
message: self.input.read().clone(),
|
message: self.input.read().clone(),
|
||||||
},
|
},
|
||||||
|resp| match resp.unwrap() {
|
|resp| match resp {
|
||||||
|
Ok(resp) => match resp {
|
||||||
SendSMSResponse::MessageRefrence(refrence) => Box::new(TextState {
|
SendSMSResponse::MessageRefrence(refrence) => Box::new(TextState {
|
||||||
text: format!("SMS sent\nRef: {}", refrence),
|
text: format!("SMS sent\nRef: {}", refrence),
|
||||||
after: PhoneNumberState::default(),
|
after: MainMenuState::default(),
|
||||||
}),
|
}),
|
||||||
SendSMSResponse::Error => Box::new(TextState {
|
SendSMSResponse::Error => Box::new(TextState {
|
||||||
text: "ERROR!".to_owned(),
|
text: "ERROR!".to_owned(),
|
||||||
after: PhoneNumberState::default(),
|
after: MainMenuState::default(),
|
||||||
}),
|
}),
|
||||||
SendSMSResponse::ErrorMessage(msg) => Box::new(TextState {
|
SendSMSResponse::ErrorMessage(msg) => Box::new(TextState {
|
||||||
text: format!("Error:\n{}", msg),
|
text: format!("Error:\n{}", msg),
|
||||||
after: PhoneNumberState::default(),
|
after: MainMenuState::default(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Err(err) => Box::new(TextState {
|
||||||
|
text: format!("Error:\n{:?}", err),
|
||||||
|
after: MainMenuState::default(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, data: &mut StateData) {
|
fn draw(&self, data: &mut StateData) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user