From b93b35700773fbc6495fb2569fda1f89120b1010 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Thu, 30 Jun 2022 23:54:24 +0200 Subject: [PATCH 01/36] tcp client stack and some improvements --- Cargo.toml | 3 +- src/command.rs | 12 +++++- src/main.rs | 67 +++++++++------------------- src/modem.rs | 115 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 124 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0150732..cf4a828 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,9 @@ anyhow = "1.0.57" embedded-hal = "0.2.7" esp-idf-hal = "0.37.4" esp-idf-sys = { version = "0.31.5", features = ["binstart", "native"] } -mqtt-protocol = "0.11.2" +minimq = "0.5.3" nb = "1.0.0" +std-embedded-time = "0.1.0" [build-dependencies] embuild = "0.29" diff --git a/src/command.rs b/src/command.rs index f44a914..b49d945 100644 --- a/src/command.rs +++ b/src/command.rs @@ -327,9 +327,9 @@ impl Command { } } - pub fn tcp_set_manual_receive() -> Command { + pub fn tcp_set_manual_receive(is_manual: bool) -> Command { Command { - text: "AT+CIPRXGET=1".to_string(), + text: format!("AT+CIPRXGET={}", is_manual as u8), timeout: Duration::from_millis(3000), contains: Some("OK".to_string()), } @@ -342,4 +342,12 @@ impl Command { contains: Some("CLOSE OK".to_string()), } } + + pub fn tcp_connection_state() -> Command { + Command { + text: "AT+CIPSTATUS".to_string(), + timeout: Duration::from_millis(2000), + contains: Some("STATE".to_string()), + } + } } diff --git a/src/main.rs b/src/main.rs index 292a6f1..3bd003f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,17 +4,11 @@ mod modem; mod command; use anyhow; -use std::time::Duration; -use std::thread; -use esp_idf_hal::delay; use esp_idf_hal::prelude::*; use esp_idf_hal::peripherals::Peripherals; use esp_idf_hal::serial; -use mqtt::control::ConnectReturnCode; -use mqtt::packet::{ConnackPacket, ConnectPacket, PublishPacketRef, QoSWithPacketIdentifier}; -use mqtt::{Decodable, Encodable, TopicName}; - +use minimq::{Minimq, QoS, Retain}; fn main() -> anyhow::Result<()> { esp_idf_sys::link_patches(); @@ -68,50 +62,29 @@ fn main() -> anyhow::Result<()> { let _ = mdm.tcp_ssl_disable()?; } let _ = mdm.tcp_set_quick_mode(false); - let _ = mdm.tcp_set_manual_receive()?; - let _ = mdm.tcp_connect("51.158.66.64", 9988)?; + let _ = mdm.tcp_set_manual_receive(true)?; - let client_id = "e-bike-tracker"; - let mut conn = ConnectPacket::new(client_id); - conn.set_clean_session(true); - let mut buf = Vec::new(); - let _ = conn.encode(&mut buf)?; + let mut mqtt: Minimq<_, _, 256, 16> = Minimq::new( + "51.158.66.64".parse().unwrap(), + "e-bike-tracker", + mdm, + std_embedded_time::StandardClock::default()).unwrap(); - let _ = mdm.tcp_send(&mut buf)?; - drop(buf); + mqtt.client + .set_will( + "exit", + "Test complete".as_bytes(), + QoS::AtMostOnce, + Retain::NotRetained, + &[], + ) + .unwrap(); - println!("+++++++++++++++++++++++++++++++++"); - let size = mdm.tcp_receive_reply_len()?; - - let mut reply = vec![0 as u8; size]; - let received_size = mdm.tcp_receive(&mut reply)?; - - println!("expected: {} / received: {}", size, received_size); - println!("+++++++++++++++++++++++++++++++++"); - drop(reply); - - let topic = TopicName::new("location")?; + println!("created mqtt client ... "); let message = "{\"lat\": 20, \"long\": 44}"; - let qos = QoSWithPacketIdentifier::Level0; - - let publish_packet = PublishPacketRef::new(&topic, qos, message.as_bytes()); - let mut buf = Vec::new(); - publish_packet.encode(&mut buf)?; - let _ = mdm.tcp_send(&mut buf)?; - drop(buf); - - thread::sleep(Duration::from_millis(300)); - let size = mdm.tcp_receive_reply_len()?; - - let mut reply = vec![0 as u8; size]; - let received_size = mdm.tcp_receive(&mut reply)?; - println!("expected: {} / received: {}", size, received_size); - println!("+++++++++++++++++++++++++++++++++"); - println!("REPLY({}) = {}", reply.len(), reply.iter().map(|b| char::from(*b)).collect::()); - println!("+++++++++++++++++++++++++++++++++"); - drop(reply); - - let _ = mdm.tcp_close_connection()?; + println!("message = {}", message); + mqtt.client.publish("devices/location", message.as_bytes(), QoS::AtMostOnce, Retain::NotRetained, &[]).unwrap(); + println!("published message ... "); } Ok(()) diff --git a/src/modem.rs b/src/modem.rs index c7f86b7..19cd4b1 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -7,6 +7,7 @@ use std::time::{Duration, Instant}; use embedded_hal::serial::{Read, Write}; use embedded_hal::digital::v2::OutputPin; use esp_idf_hal::serial::{self, Rx, Tx}; +use minimq::embedded_nal::{TcpClientStack, SocketAddr}; const MAX_TCP_MANUAL_REPLY_SIZE: usize = 300; @@ -182,7 +183,7 @@ impl Modem { self.read_response(cmd.contains, cmd.timeout) } - fn send_data(&mut self, buf: &[u8]) -> Result { + fn send_data(&mut self, buf: &[u8]) -> Result { self.rx.clear(); let _ = self.send_bytes("AT+CIPSEND".as_bytes(), '\r')?; let send_request: String = self.rx.reset(Duration::from_millis(3000)) @@ -194,15 +195,8 @@ impl Modem { } self.send_bytes(buf, 26 as char)?; // 26_u8 = Ctrl+z - to end sending data - let _ = self.read_response(Some("DATA ACCEPT".to_string()), Duration::from_millis(3000)); - - self.rx.clear(); - let res = self.send_command(Command { - text: "AT+CIPACK".to_string(), - contains: Some("OK".to_string()), - timeout: Duration::from_millis(3000), - })?; - Ok(res) + let _ = self.read_response(Some("SEND OK".to_string()), Duration::from_millis(1000))?; + Ok(buf.len()) } pub fn get_ip_addr(&mut self) -> Result { @@ -255,14 +249,13 @@ impl Modem { Ok(()) } - pub fn tcp_set_manual_receive(&mut self) -> Result<()> { - self.send_command(Command::tcp_set_manual_receive())?; + pub fn tcp_set_manual_receive(&mut self, is_manual: bool) -> Result<()> { + self.send_command(Command::tcp_set_manual_receive(is_manual))?; Ok(()) } - pub fn tcp_send(&mut self, buf: &[u8]) -> Result<()> { - self.send_data(buf)?; - Ok(()) + pub fn tcp_send(&mut self, buf: &[u8]) -> Result { + self.send_data(buf) } fn tcp_parse_response_size(&mut self, reply_line: &str) -> Result { @@ -287,30 +280,106 @@ impl Modem { pub fn tcp_receive(&mut self, buf: &mut [u8]) -> Result { let mut size = 0; loop { - let reply: usize = self.send_command(Command::tcp_receive(MAX_TCP_MANUAL_REPLY_SIZE)) + let reply_len: usize = self.send_command(Command::tcp_receive(MAX_TCP_MANUAL_REPLY_SIZE)) .map(|reply: String| { reply.lines() - .map(|line| { + .fold(0, |acc, line| { if !line.starts_with("\r") && !line.contains("+CIPRXGET: 2,") && !line.contains("OK") { - line.chars().enumerate().map(|(idx, c)| { buf[size + idx] = c as u8; }).count() + acc += line.chars().enumerate().map(|(idx, c)| { buf[size + idx] = c as u8; }).count() } else { 0 } }) - .sum() })?; - if reply == 0 { + if reply_len == 0 { break Ok(size) } else { - size += reply; + size += reply_len; continue } } } - pub fn tcp_close_connection(&mut self) -> Result { - self.send_command(Command::tcp_close()) + pub fn tcp_close_connection(&mut self) -> Result<()> { + self.send_command(Command::tcp_close())?; + Ok(()) + } + + pub fn tcp_is_connected(&mut self) -> Result { + let response = self.send_command(Command::tcp_connection_state())?; + let state = response.lines().last().and_then(|line| line.split(",").last()); + Ok(state.unwrap_or("CLOSED") == "CONNECTED") + } +} + +pub struct ModemTcpStack; + +pub struct ModemSocket { + state: SocketState, +} + +pub enum SocketState { + Building, + Connected(Modem), +} + +impl SocketState { + fn new() -> Self { + Self::Building + } + + fn get_running(&mut self) -> std::io::Result<&mut T> { + match self { + SocketState::Connected(ref mut s) => Ok(s), + _ => OutOfOrder.into(), + } + } +} + +impl ModemSocket { + fn new() -> Self { + Self { + state: SocketState::new(), + } + } + + fn get_running(s: Modem) -> Self { + Self { + state: SocketState::Connected(s) + } + } +} + +impl TcpClientStack for ModemTcpStack { + type Error = ModemError; + type TcpSocket = ModemSocket; + + fn socket(&mut self) -> Result { + Ok(self.modem) + } + + fn connect(&mut self, socket: &mut Self::TcpSocket, remote: SocketAddr) -> std::result::Result<(), nb::Error> { + self.tcp_connect(&format!("{}", remote.ip()), remote.port()) + .map_err(|err| nb::Error::Other(err)) + } + + fn is_connected(&mut self, _socket: &Self::TcpSocket) -> Result { + self.tcp_is_connected() + } + + fn send(&mut self, _socket: &mut Self::TcpSocket, buffer: &[u8]) -> std::result::Result> { + self.tcp_send(buffer) + .map_err(|err| nb::Error::Other(err)) + } + + fn receive( &mut self, _socket: &mut Self::TcpSocket, buffer: &mut [u8]) -> std::result::Result> { + self.tcp_receive(buffer) + .map_err(|err| nb::Error::Other(err)) + } + + fn close(&mut self, _socket: Self::TcpSocket) -> Result<()> { + self.tcp_close_connection() } } From a215c628a794b5fd471051542bce897b7e17ecfb Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sun, 3 Jul 2022 02:27:36 +0200 Subject: [PATCH 02/36] http post - not working because sim800l supports tlsv1.0 only --- src/command.rs | 126 ++++++++++++++++++++++++++++++++++--------------- src/main.rs | 92 +++++++++++++++--------------------- src/modem.rs | 68 ++++++++++++++++++++------ 3 files changed, 179 insertions(+), 107 deletions(-) diff --git a/src/command.rs b/src/command.rs index f44a914..e8e6a49 100644 --- a/src/command.rs +++ b/src/command.rs @@ -71,14 +71,6 @@ impl Command { } } - pub fn gprs_open() -> Command { - Command { - text: "AT+SAPBR=1,1".to_string(), - timeout: Duration::from_millis(3000), - contains: Some("OK".to_string()), - } - } - pub fn gprs_set_apn(apn: &str) -> Command { Command { text: format!("AT+SAPBR=3,1,\"APN\",\"{}\"", apn), @@ -103,7 +95,7 @@ impl Command { } } - pub fn getbear() -> Command { + pub fn gprs_bearer_status() -> Command { Command { text: "AT+SAPBR=2,1".to_string(), timeout: Duration::from_millis(3000), @@ -111,6 +103,22 @@ impl Command { } } + pub fn gprs_bearer_open() -> Command { + Command { + text: "AT+SAPBR=1,1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + + pub fn gprs_bearer_close() -> Command { + Command { + text: "AT+SAPBR=0,1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + pub fn get_local_ip_addr() -> Command { Command { text: "AT+CIFSR".to_string(), @@ -135,7 +143,7 @@ impl Command { } } - pub fn http_set() -> Command { + pub fn http_set_cid() -> Command { Command { text: "AT+HTTPPARA=\"CID\",1".to_string(), timeout: Duration::from_millis(3000), @@ -159,9 +167,25 @@ impl Command { } } - pub fn http_init_url() -> Command { + pub fn http_init_url(url: &str) -> Command { Command { - text: "AT+HTTPPARA=\"URL\",\"{}\"".to_string(), + text: format!("AT+HTTPPARA=\"URL\",\"{}\"", url), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + + pub fn http_set_ssl(enabled: bool) -> Command { + Command { + text: format!("AT+HTTPSSL={}", enabled as u8), + timeout: Duration::from_millis(1000), + contains: Some("OK".to_string()), + } + } + + pub fn http_set_header(header: &str, value: &str) -> Command { + Command { + text: format!("AT+HTTPPARA=\"USERDATA\",\"{}: {}\"", header, value), timeout: Duration::from_millis(3000), contains: Some("OK".to_string()), } @@ -175,31 +199,31 @@ impl Command { } } - pub fn http_set_content() -> Command { + pub fn http_set_content(content: &str) -> Command { Command { - text: "AT+HTTPPARA=\"CONTENT\",\"{}\"".to_string(), + text: format!("AT+HTTPPARA=\"CONTENT\",\"{}\"", content), timeout: Duration::from_millis(3000), contains: Some("OK".to_string()), } } - pub fn http_post_len() -> Command { + pub fn http_post_len(size: usize, time: usize) -> Command { Command { - text: "AT+HTTPDATA={}5000".to_string(), - timeout: Duration::from_millis(3000), - contains: Some("DOWNLOAD".to_string()), + text: format!("AT+HTTPDATA={},{}", size, time), + timeout: Duration::from_millis(5000), + contains: Some("OK".to_string()), } } pub fn http_post() -> Command { Command { text: "AT+HTTPACTION=1".to_string(), - timeout: Duration::from_millis(3000), - contains: Some("+HTTPACTION".to_string()), + timeout: Duration::from_millis(10000), + contains: Some("HTTPACTION".to_string()), } } - pub fn http_get_data() -> Command { + pub fn http_response() -> Command { Command { text: "AT+HTTPREAD".to_string(), timeout: Duration::from_millis(3000), @@ -207,7 +231,7 @@ impl Command { } } - pub fn closehttp() -> Command { + pub fn http_close() -> Command { Command { text: "AT+HTTPTERM".to_string(), timeout: Duration::from_millis(3000), @@ -215,19 +239,11 @@ impl Command { } } - pub fn closebear() -> Command { - Command { - text: "AT+SAPBR=0,1".to_string(), - timeout: Duration::from_millis(3000), - contains: Some("OK".to_string()), - } - } - pub fn probe() -> Command { Command { text: "AT".to_string(), timeout: Duration::from_millis(3000), - contains: Some("+CIEV".to_string()), + contains: Some("OK".to_string()), } } @@ -247,14 +263,6 @@ impl Command { } } - pub fn tcp_ssl_enable() -> Command { - Command { - text: "AT+CIPSSL=1".to_string(), - timeout: Duration::from_millis(3000), - contains: Some("OK".to_string()), - } - } - pub fn tcp_ssl_check() -> Command { Command { text: "AT+CIPSSL=?".to_string(), @@ -342,4 +350,44 @@ impl Command { contains: Some("CLOSE OK".to_string()), } } + + pub fn manufacturer_id() -> Command { + Command { + text: "AT+GMI".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + + pub fn model_id() -> Command { + Command { + text: "AT+GMM".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + + pub fn release_id() -> Command { + Command { + text: "AT+GMR".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + + pub fn get_location() -> Command { + Command { + text: "AT+CLBS=1,1".to_string(), + timeout: Duration::from_millis(10000), + contains: Some("OK".to_string()), + } + } + + pub fn ssl_opt() -> Command { + Command { + text: "AT+SSLOPT=1,1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } } diff --git a/src/main.rs b/src/main.rs index 292a6f1..a441730 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,68 +50,52 @@ fn main() -> anyhow::Result<()> { mdm.init(modem_pwrkey, modem_rst, modem_power)?; if !mdm.is_gprs_attached()? { - let _ = mdm.connect_to_gprs_ap( + let _ = mdm.gprs_attach_ap( config::A1_GPRS_AP.apn, config::A1_GPRS_AP.username, config::A1_GPRS_AP.password, )?; } - if mdm.is_gprs_attached()? { - let _ = mdm.get_ip_addr()?; + thread::sleep(Duration::from_millis(2000)); - //println!("connecting to server!"); - //if !mdm.tcp_is_ssl_enabled()? { - // let _ = mdm.tcp_ssl_enable()?; - //} - if mdm.tcp_is_ssl_enabled()? { - let _ = mdm.tcp_ssl_disable()?; + let _ = mdm.chip_info()?; + + loop { + if mdm.is_gprs_attached()? { + let _ = mdm.gprs_connect()?; + for _ in 0..3 { + let ip_addr = mdm.gprs_status()?; + if ip_addr.contains("0.0.0.0") { + thread::sleep(Duration::from_millis(2000)); + } else { + break + } + } + + let _ = mdm.location()?; + let _ = mdm.ssl_opt()?; + + println!("connecting to server!"); + //if !mdm.tcp_is_ssl_enabled()? { + // let _ = mdm.tcp_ssl_enable()?; + //} + //if mdm.tcp_is_ssl_enabled()? { + // let _ = mdm.tcp_ssl_disable()?; + //} + // + let message = "{\"lat\": 20.475370, \"long\": 44.747224}"; + let url = "https://a.tracker.called.quest"; + //let url = "https://rest.iot.fr-par.scw.cloud"; + let token = "eyJOZXRJRCI6ImUwYTc5YzM2LWExZjYtNDNiMC1hMGY3LWE2YTk1OGI3Zjk1ZCIsIk5ldEtleSI6IjZhZGU2ZWZkLThiMGYtNDQ3ZC1hNWY5LThkNDJjNDk4NDQ3MSJ9"; + let reply = mdm.http_post(url, token, message.as_bytes())?; + println!("+++++++++++++++++++++++++++++++++"); + println!("REPLY({}) = {}", reply.len(), reply); + println!("+++++++++++++++++++++++++++++++++"); + let _ = mdm.http_close()?; + + break } - let _ = mdm.tcp_set_quick_mode(false); - let _ = mdm.tcp_set_manual_receive()?; - let _ = mdm.tcp_connect("51.158.66.64", 9988)?; - - let client_id = "e-bike-tracker"; - let mut conn = ConnectPacket::new(client_id); - conn.set_clean_session(true); - let mut buf = Vec::new(); - let _ = conn.encode(&mut buf)?; - - let _ = mdm.tcp_send(&mut buf)?; - drop(buf); - - println!("+++++++++++++++++++++++++++++++++"); - let size = mdm.tcp_receive_reply_len()?; - - let mut reply = vec![0 as u8; size]; - let received_size = mdm.tcp_receive(&mut reply)?; - - println!("expected: {} / received: {}", size, received_size); - println!("+++++++++++++++++++++++++++++++++"); - drop(reply); - - let topic = TopicName::new("location")?; - let message = "{\"lat\": 20, \"long\": 44}"; - let qos = QoSWithPacketIdentifier::Level0; - - let publish_packet = PublishPacketRef::new(&topic, qos, message.as_bytes()); - let mut buf = Vec::new(); - publish_packet.encode(&mut buf)?; - let _ = mdm.tcp_send(&mut buf)?; - drop(buf); - - thread::sleep(Duration::from_millis(300)); - let size = mdm.tcp_receive_reply_len()?; - - let mut reply = vec![0 as u8; size]; - let received_size = mdm.tcp_receive(&mut reply)?; - println!("expected: {} / received: {}", size, received_size); - println!("+++++++++++++++++++++++++++++++++"); - println!("REPLY({}) = {}", reply.len(), reply.iter().map(|b| char::from(*b)).collect::()); - println!("+++++++++++++++++++++++++++++++++"); - drop(reply); - - let _ = mdm.tcp_close_connection()?; } Ok(()) diff --git a/src/modem.rs b/src/modem.rs index c7f86b7..190eaea 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -165,26 +165,25 @@ impl Modem { } #[inline(always)] - fn send_bytes(&mut self, payload: &[u8], eos: char) -> Result<()> { + fn send_bytes(&mut self, payload: &[u8], eos: Option) -> Result<()> { + self.rx.clear(); for b in payload.iter() { nb::block!(self.tx.write(*b)) .map_err(|_| ModemError::CommandError(format!("error writing {} to serial", b)))?; } - nb::block!(self.tx.write(eos as u8)) - .map_err(|_| ModemError::CommandError(format!("error writing {} to serial", eos)))?; + eos.map(|b| nb::block!(self.tx.write(b))); Ok(()) } fn send_command(&mut self, cmd: Command) -> Result { println!("-----------------------------------------------------------"); println!("Sending {} ...", cmd.text); - let _ = self.send_bytes(cmd.text.as_bytes(), '\r')?; + let _ = self.send_bytes(cmd.text.as_bytes(), Some('\r' as u8))?; self.read_response(cmd.contains, cmd.timeout) } - fn send_data(&mut self, buf: &[u8]) -> Result { - self.rx.clear(); - let _ = self.send_bytes("AT+CIPSEND".as_bytes(), '\r')?; + fn tcp_send_data(&mut self, buf: &[u8]) -> Result { + let _ = self.send_bytes("AT+CIPSEND".as_bytes(), Some('\r' as u8))?; let send_request: String = self.rx.reset(Duration::from_millis(3000)) .map(char::from) .take_while(|c| *c != '>').collect(); @@ -193,10 +192,9 @@ impl Modem { return Err(ModemError::SendDataError); } - self.send_bytes(buf, 26 as char)?; // 26_u8 = Ctrl+z - to end sending data + self.send_bytes(buf, Some(26))?; // 26_u8 = Ctrl+z - to end sending data let _ = self.read_response(Some("DATA ACCEPT".to_string()), Duration::from_millis(3000)); - self.rx.clear(); let res = self.send_command(Command { text: "AT+CIPACK".to_string(), contains: Some("OK".to_string()), @@ -205,11 +203,11 @@ impl Modem { Ok(res) } - pub fn get_ip_addr(&mut self) -> Result { - self.send_command(Command::getbear()) + pub fn gprs_status(&mut self) -> Result { + self.send_command(Command::gprs_bearer_status()) } - pub fn connect_to_gprs_ap(&mut self, apn: &str, username: &str, password: &str)-> Result<()> { + pub fn gprs_attach_ap(&mut self, apn: &str, username: &str, password: &str)-> Result<()> { println!("init gprs ..."); let _ = self.send_command(Command::gprs_init())?; @@ -219,8 +217,12 @@ impl Modem { let _ = self.send_command(Command::gprs_set_user(username))?; let _ = self.send_command(Command::gprs_set_pwd(password))?; + Ok(()) + } + + pub fn gprs_connect(&mut self)-> Result<()> { println!("open gprs ..."); - let _ = self.send_command(Command::gprs_open())?; + let _ = self.send_command(Command::gprs_bearer_open())?; Ok(()) } @@ -261,7 +263,7 @@ impl Modem { } pub fn tcp_send(&mut self, buf: &[u8]) -> Result<()> { - self.send_data(buf)?; + self.tcp_send_data(buf)?; Ok(()) } @@ -313,4 +315,42 @@ impl Modem { pub fn tcp_close_connection(&mut self) -> Result { self.send_command(Command::tcp_close()) } + + pub fn http_post(&mut self, url: &str, token: &str, content: &[u8]) -> Result { + let _ = self.send_command(Command::http_init()); + let _ = self.send_command(Command::http_set_ssl(true)); + let _ = self.send_command(Command::http_set_cid()); + let _ = self.send_command(Command::http_init_url(url)); + let _ = self.send_command(Command::http_set_header("X-Secret", token)); + let _ = self.send_command(Command::http_set_header("X-Topic", "device-dev")); + let _ = self.send_command(Command::http_set_content("application/json")); + let _ = self.send_command(Command::http_post_len(content.len(), 100000)); + let _ = self.send_bytes(content, Some(26)); + let _ = self.send_command(Command::http_post()); + self.send_command(Command::http_response()) + } + + pub fn http_close(&mut self) -> Result<()> { + let _ = self.send_command(Command::http_close())?; + Ok(()) + } + + pub fn chip_info(&mut self) -> Result<()> { + let _ = self.send_command(Command::manufacturer_id())?; + thread::sleep(Duration::from_millis(1000)); + let _ = self.send_command(Command::model_id())?; + thread::sleep(Duration::from_millis(1000)); + let _ = self.send_command(Command::release_id())?; + Ok(()) + } + + pub fn location(&mut self) -> Result<()> { + let _ = self.send_command(Command::get_location())?; + Ok(()) + } + + pub fn ssl_opt(&mut self) -> Result<()> { + let _ = self.send_command(Command::ssl_opt())?; + Ok(()) + } } From d023f5db76fd00c2710fcf75cba93efb51226902 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 4 Jul 2022 17:04:09 +0200 Subject: [PATCH 03/36] mqtt works ... connect + publish --- src/command.rs | 98 ++++++++++++++++++++++++++++------------ src/main.rs | 71 +++++++++++++++++++++-------- src/modem.rs | 120 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 202 insertions(+), 87 deletions(-) diff --git a/src/command.rs b/src/command.rs index e8e6a49..cfebef1 100644 --- a/src/command.rs +++ b/src/command.rs @@ -151,23 +151,7 @@ impl Command { } } - pub fn http_enable_ssl() -> Command { - Command { - text: "AT+HTTPSSL=1".to_string(), - timeout: Duration::from_millis(3000), - contains: Some("OK".to_string()), - } - } - - pub fn http_disable_ssl() -> Command { - Command { - text: "AT+HTTPSSL=0".to_string(), - timeout: Duration::from_millis(3000), - contains: Some("OK".to_string()), - } - } - - pub fn http_init_url(url: &str) -> Command { + pub fn http_set_url(url: &str) -> Command { Command { text: format!("AT+HTTPPARA=\"URL\",\"{}\"", url), timeout: Duration::from_millis(3000), @@ -199,6 +183,14 @@ impl Command { } } + pub fn http_post() -> Command { + Command { + text: "AT+HTTPACTION=1".to_string(), + timeout: Duration::from_millis(10000), + contains: Some("HTTPACTION".to_string()), + } + } + pub fn http_set_content(content: &str) -> Command { Command { text: format!("AT+HTTPPARA=\"CONTENT\",\"{}\"", content), @@ -207,6 +199,14 @@ impl Command { } } + pub fn http_set_redirect(redirect: bool) -> Command { + Command { + text: format!("AT+HTTPPARA=\"REDIR\",\"{}\"", redirect as u8), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + pub fn http_post_len(size: usize, time: usize) -> Command { Command { text: format!("AT+HTTPDATA={},{}", size, time), @@ -215,15 +215,7 @@ impl Command { } } - pub fn http_post() -> Command { - Command { - text: "AT+HTTPACTION=1".to_string(), - timeout: Duration::from_millis(10000), - contains: Some("HTTPACTION".to_string()), - } - } - - pub fn http_response() -> Command { + pub fn http_read_response() -> Command { Command { text: "AT+HTTPREAD".to_string(), timeout: Duration::from_millis(3000), @@ -243,7 +235,7 @@ impl Command { Command { text: "AT".to_string(), timeout: Duration::from_millis(3000), - contains: Some("OK".to_string()), + contains: Some("+CIEV".to_string()), } } @@ -335,9 +327,9 @@ impl Command { } } - pub fn tcp_set_manual_receive() -> Command { + pub fn tcp_set_manual_receive(is_manual: bool) -> Command { Command { - text: "AT+CIPRXGET=1".to_string(), + text: format!("AT+CIPRXGET={}", is_manual as u8), timeout: Duration::from_millis(3000), contains: Some("OK".to_string()), } @@ -390,4 +382,52 @@ impl Command { contains: Some("OK".to_string()), } } + + pub fn ssl_set_client_cert(path: &str, password: &str) -> Command { + Command { + text: format!("AT+SSLSETCERT={},{}", path, password), + timeout: Duration::from_millis(2000), + contains: Some("SSLSETCERT".to_string()), + } + } + + pub fn ssl_set_root_cert(path: &str, size: usize) -> Command { + Command { + text: format!("AT+SSLSETROOT={},{}", path, size), + timeout: Duration::from_millis(2000), + contains: Some("SSLSETCERT".to_string()), + } + } + + pub fn fs_file_create(path: &str) -> Command { + Command { + text: format!("AT+FSCREATE={}", path), + timeout: Duration::from_millis(2000), + contains: Some("OK".to_string()), + } + } + + pub fn fs_file_write(path: &str, append: bool, size: usize, input_time_sec: usize) -> Command { + Command { + text: format!("AT+FSWRITE={},{},{},{}", path, append as u8, size, input_time_sec), + timeout: Duration::from_millis(20000), + contains: Some("OK".to_string()), + } + } + + pub fn fs_list(path: &str) -> Command { + Command { + text: format!("AT+FSLS={}", path), + timeout: Duration::from_millis(2000), + contains: Some("OK".to_string()), + } + } + + pub fn fs_free_size() -> Command { + Command { + text: "AT+FSMEM".to_string(), + timeout: Duration::from_millis(2000), + contains: Some("OK".to_string()), + } + } } diff --git a/src/main.rs b/src/main.rs index a441730..8e70b67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,13 +6,12 @@ mod command; use anyhow; use std::time::Duration; use std::thread; -use esp_idf_hal::delay; use esp_idf_hal::prelude::*; use esp_idf_hal::peripherals::Peripherals; use esp_idf_hal::serial; use mqtt::control::ConnectReturnCode; -use mqtt::packet::{ConnackPacket, ConnectPacket, PublishPacketRef, QoSWithPacketIdentifier}; +use mqtt::packet::{ConnackPacket, ConnectPacket, PublishPacket, QoSWithPacketIdentifier}; use mqtt::{Decodable, Encodable, TopicName}; @@ -59,7 +58,13 @@ fn main() -> anyhow::Result<()> { thread::sleep(Duration::from_millis(2000)); - let _ = mdm.chip_info()?; + //println!("setting up client TLS cert"); + //let client_cert = include_bytes!("../certs/full-bin.p12"); + //let client_cert_path = "C:\\USER\\fullchain.pem"; + + //let _ = mdm.upload_cert(client_cert_path, client_cert)?; + //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; + //let _ = mdm.fs_list("C:\\USER\\")?; loop { if mdm.is_gprs_attached()? { @@ -73,26 +78,54 @@ fn main() -> anyhow::Result<()> { } } - let _ = mdm.location()?; - let _ = mdm.ssl_opt()?; - - println!("connecting to server!"); + //println!("connecting to server!"); //if !mdm.tcp_is_ssl_enabled()? { // let _ = mdm.tcp_ssl_enable()?; //} - //if mdm.tcp_is_ssl_enabled()? { - // let _ = mdm.tcp_ssl_disable()?; - //} - // + if mdm.tcp_is_ssl_enabled()? { + let _ = mdm.tcp_ssl_disable()?; + } + + let client_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; let message = "{\"lat\": 20.475370, \"long\": 44.747224}"; - let url = "https://a.tracker.called.quest"; - //let url = "https://rest.iot.fr-par.scw.cloud"; - let token = "eyJOZXRJRCI6ImUwYTc5YzM2LWExZjYtNDNiMC1hMGY3LWE2YTk1OGI3Zjk1ZCIsIk5ldEtleSI6IjZhZGU2ZWZkLThiMGYtNDQ3ZC1hNWY5LThkNDJjNDk4NDQ3MSJ9"; - let reply = mdm.http_post(url, token, message.as_bytes())?; - println!("+++++++++++++++++++++++++++++++++"); - println!("REPLY({}) = {}", reply.len(), reply); - println!("+++++++++++++++++++++++++++++++++"); - let _ = mdm.http_close()?; + + let _ = mdm.tcp_set_quick_mode(false); + let _ = mdm.tcp_set_manual_receive(true); + + let _ = mdm.tcp_connect("51.158.66.64", 1883)?; + + let mut buf = Vec::new(); + + let mut conn = ConnectPacket::new(client_id); + conn.set_clean_session(true); + conn.set_keep_alive(0); + let _ = conn.encode(&mut buf)?; + let _ = mdm.tcp_send(&mut buf)?; + buf.clear(); + + thread::sleep(Duration::from_millis(1000)); + + let size = mdm.tcp_receive_reply_len()?; + let mut reply = vec![0 as u8; size]; + let _ = mdm.tcp_receive(&mut reply); + println!("TCP Received: {}", reply.iter().map(|b| char::from(*b)).collect::()); + + println!("sending publish packet"); + let packet = PublishPacket::new( + TopicName::new("bajsevi/test").unwrap(), + QoSWithPacketIdentifier::Level0, + message.as_bytes(), + ); + let _ = packet.encode(&mut buf)?; + let _ = mdm.tcp_send(&mut buf)?; + buf.clear(); + + let size = mdm.tcp_receive_reply_len()?; + let mut reply = vec![0 as u8; size]; + let _ = mdm.tcp_receive(&mut reply); + println!("TCP Received: {}", reply.iter().map(|b| char::from(*b)).collect::()); + + let _ = mdm.tcp_close_connection()?; break } diff --git a/src/modem.rs b/src/modem.rs index 190eaea..9a12acb 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -140,12 +140,10 @@ impl Modem { Ok(()) } - /// Reads the serial RX until a \\n char is encoutered, or a timeout is reached. The timeout is - /// provided on input via the `timeout` argument. The first argument `contains` is checked - /// against a line in the response, if it's there the reading stops. - /// - /// If `contains` is `None`, the first line only is returned in the response. If it's - /// `Some(match_txt)`, then the end of the response is matched against `match_txt`. + /// Reads the serial RX until the `contains` string is encoutered if `contains` is Some(s), if + /// None, then the first line is returned. If a timeout is reached. The timeout is provided on + /// input via the `timeout` argument. The first argument `contains` is checked against every + /// line in the response. fn read_response(&mut self, contains: Option, timeout: Duration) -> Result { let mut response = String::new(); let start = Instant::now(); @@ -166,7 +164,7 @@ impl Modem { #[inline(always)] fn send_bytes(&mut self, payload: &[u8], eos: Option) -> Result<()> { - self.rx.clear(); + //self.rx.clear(); for b in payload.iter() { nb::block!(self.tx.write(*b)) .map_err(|_| ModemError::CommandError(format!("error writing {} to serial", b)))?; @@ -188,19 +186,13 @@ impl Modem { .map(char::from) .take_while(|c| *c != '>').collect(); - if send_request == "" { + if send_request != "\r\n" { + println!("{:?}", send_request.as_bytes()); return Err(ModemError::SendDataError); } self.send_bytes(buf, Some(26))?; // 26_u8 = Ctrl+z - to end sending data - let _ = self.read_response(Some("DATA ACCEPT".to_string()), Duration::from_millis(3000)); - - let res = self.send_command(Command { - text: "AT+CIPACK".to_string(), - contains: Some("OK".to_string()), - timeout: Duration::from_millis(3000), - })?; - Ok(res) + self.read_response(Some("SEND OK".to_string()), Duration::from_millis(3000)) } pub fn gprs_status(&mut self) -> Result { @@ -257,8 +249,8 @@ impl Modem { Ok(()) } - pub fn tcp_set_manual_receive(&mut self) -> Result<()> { - self.send_command(Command::tcp_set_manual_receive())?; + pub fn tcp_set_manual_receive(&mut self, is_manual: bool) -> Result<()> { + self.send_command(Command::tcp_set_manual_receive(is_manual))?; Ok(()) } @@ -289,25 +281,22 @@ impl Modem { pub fn tcp_receive(&mut self, buf: &mut [u8]) -> Result { let mut size = 0; loop { - let reply: usize = self.send_command(Command::tcp_receive(MAX_TCP_MANUAL_REPLY_SIZE)) - .map(|reply: String| { - reply.lines() - .map(|line| { - if !line.starts_with("\r") && !line.contains("+CIPRXGET: 2,") && !line.contains("OK") { - line.chars().enumerate().map(|(idx, c)| { buf[size + idx] = c as u8; }).count() - } - else { - 0 - } - }) - .sum() + let reply = self.send_command(Command::tcp_receive(MAX_TCP_MANUAL_REPLY_SIZE)) + .map(|reply| { + reply + .split("\r\n") + .filter(|line| line.len() > 2 && !line.contains("+CIPRXGET: 2,")) + .next() + .map(|line| line.chars().enumerate().map(|(idx, c)| buf[size + idx] = c as u8).count()) })?; - if reply == 0 { - break Ok(size) - } - else { - size += reply; - continue + match reply { + Some(0) | None => { + break Ok(size) + }, + Some(x) => { + size += x; + continue + }, } } } @@ -318,16 +307,26 @@ impl Modem { pub fn http_post(&mut self, url: &str, token: &str, content: &[u8]) -> Result { let _ = self.send_command(Command::http_init()); - let _ = self.send_command(Command::http_set_ssl(true)); let _ = self.send_command(Command::http_set_cid()); - let _ = self.send_command(Command::http_init_url(url)); + let _ = self.send_command(Command::http_set_url(url)); let _ = self.send_command(Command::http_set_header("X-Secret", token)); let _ = self.send_command(Command::http_set_header("X-Topic", "device-dev")); let _ = self.send_command(Command::http_set_content("application/json")); + let _ = self.send_command(Command::http_set_ssl(true)); let _ = self.send_command(Command::http_post_len(content.len(), 100000)); let _ = self.send_bytes(content, Some(26)); let _ = self.send_command(Command::http_post()); - self.send_command(Command::http_response()) + self.send_command(Command::http_read_response()) + } + + pub fn http_get(&mut self, url: &str) -> Result { + let _ = self.send_command(Command::http_init()); + let _ = self.send_command(Command::http_set_cid()); + let _ = self.send_command(Command::http_set_url(url)); + let _ = self.send_command(Command::http_set_redirect(true)); + let _ = self.send_command(Command::http_set_ssl(true)); + let _ = self.send_command(Command::http_get()); + self.send_command(Command::http_read_response()) } pub fn http_close(&mut self) -> Result<()> { @@ -353,4 +352,47 @@ impl Modem { let _ = self.send_command(Command::ssl_opt())?; Ok(()) } + + fn file_write(&mut self, buf: &[u8], path: &str, append: bool, input_time_sec: usize) -> Result<()> { + let cmd = Command::fs_file_write(path, append, buf.len(), input_time_sec); + let _ = self.send_bytes(cmd.text.as_bytes(), Some('\r' as u8))?; + let send_request: String = self.rx.reset(Duration::from_millis(3000)) + .map(char::from) + .take_while(|c| *c != '>').collect(); + + if send_request == "" { + return Err(ModemError::SendDataError); + } + + self.send_bytes(buf, None)?; + let _ = self.read_response(Some("OK".to_string()), Duration::from_millis(3000)); + + Ok(()) + } + + pub fn upload_cert(&mut self, path: &str, cert: &[u8]) -> Result<()> { + let _ = self.send_command(Command::fs_file_create(path))?; + let _ = self.file_write(cert, path, false, 20000)?; + Ok(()) + } + + pub fn fs_list(&mut self, path: &str) -> Result<()> { + let _ = self.send_command(Command::fs_list(path))?; + Ok(()) + } + + pub fn fs_free_space(&mut self) -> Result<()> { + let _ = self.send_command(Command::fs_free_size())?; + Ok(()) + } + + pub fn ssl_set_client_cert(&mut self, path: &str, password: &str) -> Result<()> { + let _ = self.send_command(Command::ssl_set_client_cert(path, password))?; + Ok(()) + } + + pub fn ssl_set_root_cert(&mut self, path: &str, filesize: usize) -> Result<()> { + let _ = self.send_command(Command::ssl_set_root_cert(path, filesize))?; + Ok(()) + } } From 7bfd37b79949e135ffa7667466e0feba2ab24736 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Wed, 6 Jul 2022 20:33:43 +0200 Subject: [PATCH 04/36] working threads with mpsc::channel --- sdkconfig.defaults | 2 +- src/accel.rs | 17 +++++ src/gps.rs | 17 +++++ src/main.rs | 135 ++++++++++----------------------------- src/modem.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 221 insertions(+), 105 deletions(-) create mode 100644 src/accel.rs create mode 100644 src/gps.rs diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 1016968..a0f1158 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,5 +1,5 @@ # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) -CONFIG_ESP_MAIN_TASK_STACK_SIZE=17000 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). diff --git a/src/accel.rs b/src/accel.rs new file mode 100644 index 0000000..772ed41 --- /dev/null +++ b/src/accel.rs @@ -0,0 +1,17 @@ +use anyhow; + +use std::sync::mpsc::SyncSender; +use std::thread; +use std::time::Duration; + +use crate::modem::Msg; + +pub fn main(sender: SyncSender) -> Result<(), anyhow::Error> { + println!("entering ACCELERATOR sender loop ..."); + for i in 0..20 { + println!("sending ACCELERATOR message ({}) of 20 ...", i); + let _ = sender.send(Msg::Location("{\"velocity\": 21.43, \"altitude\": 367}".to_string()))?; + thread::sleep(Duration::from_millis(2000)); + } + Ok(()) +} diff --git a/src/gps.rs b/src/gps.rs new file mode 100644 index 0000000..9a2460f --- /dev/null +++ b/src/gps.rs @@ -0,0 +1,17 @@ +use anyhow; + +use std::sync::mpsc::SyncSender; +use std::thread; +use std::time::Duration; + +use crate::modem::Msg; + +pub fn main(sender: SyncSender) -> Result<(), anyhow::Error> { + println!("entering GPS sender loop ..."); + for i in 0..20 { + println!("sending GPS message ({}) of 20 ...", i); + let _ = sender.send(Msg::Location("{\"lat\": 20.4322, \"long\": 44.5432}".to_string()))?; + thread::sleep(Duration::from_millis(2000)); + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 8e70b67..08d6e94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,20 @@ +mod accel; mod config; +mod gps; mod modem; #[allow(dead_code)] mod command; use anyhow; +use std::thread::{self, JoinHandle}; +use std::cell::RefCell; use std::time::Duration; -use std::thread; -use esp_idf_hal::prelude::*; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::serial; -use mqtt::control::ConnectReturnCode; -use mqtt::packet::{ConnackPacket, ConnectPacket, PublishPacket, QoSWithPacketIdentifier}; -use mqtt::{Decodable, Encodable, TopicName}; +use esp_idf_hal::peripherals::Peripherals; + +thread_local! { + static TLS: RefCell = RefCell::new(13); +} fn main() -> anyhow::Result<()> { @@ -20,116 +22,43 @@ fn main() -> anyhow::Result<()> { let dp = Peripherals::take().expect("error taking peripherals"); + println!("Rust main thread: {:?}", thread::current()); + // LilyGo TTGO T-Call sim800l board serial pins. - let serial_rx = dp.pins.gpio26; - let serial_tx = dp.pins.gpio27; - - let serial_pins = serial::Pins { - tx: serial_tx, - rx: serial_rx, - cts: None, - rts: None, - }; - - // Create the serial and panic with a message ... if we can't create the serial port, then we - // can't communicate with the sim800l module, hence we don't run anymore. - let serial: serial::Serial = serial::Serial::new( - dp.uart1, - serial_pins, - serial::config::Config::default().baudrate(Hertz(115200)), - )?; - - let (tx, rx) = serial.split(); - let mut mdm = modem::Modem::new(tx, rx); - + let modem_rx = dp.pins.gpio26; + let modem_tx = dp.pins.gpio27; + // LilyGo TTGO T-Call sim800l board power / reset pins. let modem_pwrkey = dp.pins.gpio4.into_output()?; let modem_rst = dp.pins.gpio5.into_output()?; let modem_power = dp.pins.gpio23.into_output()?; + // UART interface for the GSM modem + let modem_uart = dp.uart1; - mdm.init(modem_pwrkey, modem_rst, modem_power)?; + let mut threads: Vec>> = vec![]; - if !mdm.is_gprs_attached()? { - let _ = mdm.gprs_attach_ap( - config::A1_GPRS_AP.apn, - config::A1_GPRS_AP.username, - config::A1_GPRS_AP.password, - )?; - } + println!("Rust main thread: {:?}", thread::current()); - thread::sleep(Duration::from_millis(2000)); - //println!("setting up client TLS cert"); - //let client_cert = include_bytes!("../certs/full-bin.p12"); - //let client_cert_path = "C:\\USER\\fullchain.pem"; + TLS.with(|tls| { + println!("Main TLS before change: {}", *tls.borrow()); + }); - //let _ = mdm.upload_cert(client_cert_path, client_cert)?; - //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; - //let _ = mdm.fs_list("C:\\USER\\")?; + TLS.with(|tls| *tls.borrow_mut() = 42); - loop { - if mdm.is_gprs_attached()? { - let _ = mdm.gprs_connect()?; - for _ in 0..3 { - let ip_addr = mdm.gprs_status()?; - if ip_addr.contains("0.0.0.0") { - thread::sleep(Duration::from_millis(2000)); - } else { - break - } - } + TLS.with(|tls| { + println!("Main TLS after change: {}", *tls.borrow()); + }); - //println!("connecting to server!"); - //if !mdm.tcp_is_ssl_enabled()? { - // let _ = mdm.tcp_ssl_enable()?; - //} - if mdm.tcp_is_ssl_enabled()? { - let _ = mdm.tcp_ssl_disable()?; - } - - let client_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; - let message = "{\"lat\": 20.475370, \"long\": 44.747224}"; + let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); - let _ = mdm.tcp_set_quick_mode(false); - let _ = mdm.tcp_set_manual_receive(true); + let accel_sender = gps_sender.clone(); - let _ = mdm.tcp_connect("51.158.66.64", 1883)?; + threads.push(thread::spawn(move || gps::main(gps_sender))); + thread::sleep(Duration::from_millis(1000)); + threads.push(thread::spawn(move || accel::main(accel_sender))); + thread::sleep(Duration::from_millis(1000)); - let mut buf = Vec::new(); - - let mut conn = ConnectPacket::new(client_id); - conn.set_clean_session(true); - conn.set_keep_alive(0); - let _ = conn.encode(&mut buf)?; - let _ = mdm.tcp_send(&mut buf)?; - buf.clear(); - - thread::sleep(Duration::from_millis(1000)); - - let size = mdm.tcp_receive_reply_len()?; - let mut reply = vec![0 as u8; size]; - let _ = mdm.tcp_receive(&mut reply); - println!("TCP Received: {}", reply.iter().map(|b| char::from(*b)).collect::()); - - println!("sending publish packet"); - let packet = PublishPacket::new( - TopicName::new("bajsevi/test").unwrap(), - QoSWithPacketIdentifier::Level0, - message.as_bytes(), - ); - let _ = packet.encode(&mut buf)?; - let _ = mdm.tcp_send(&mut buf)?; - buf.clear(); - - let size = mdm.tcp_receive_reply_len()?; - let mut reply = vec![0 as u8; size]; - let _ = mdm.tcp_receive(&mut reply); - println!("TCP Received: {}", reply.iter().map(|b| char::from(*b)).collect::()); - - let _ = mdm.tcp_close_connection()?; - - break - } - } + let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; Ok(()) } diff --git a/src/modem.rs b/src/modem.rs index 9a12acb..b9a59d8 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -1,12 +1,19 @@ use crate::command::Command; +use anyhow; use std::thread; use std::error::Error; use std::time::{Duration, Instant}; +use std::sync::mpsc::Receiver; + +use esp_idf_hal::prelude::*; +use esp_idf_hal::serial::{self, Rx, Tx}; use embedded_hal::serial::{Read, Write}; use embedded_hal::digital::v2::OutputPin; -use esp_idf_hal::serial::{self, Rx, Tx}; + +use mqtt::packet::{ConnectPacket, PublishPacket, QoSWithPacketIdentifier}; +use mqtt::{Encodable, TopicName}; const MAX_TCP_MANUAL_REPLY_SIZE: usize = 300; @@ -395,4 +402,150 @@ impl Modem { let _ = self.send_command(Command::ssl_set_root_cert(path, filesize))?; Ok(()) } + + fn mqtt_receive_reply(&mut self) -> std::result::Result<(), anyhow::Error> { + println!("entered receiving modem reply ..."); + let size = self.tcp_receive_reply_len()?; + println!("receiving reply len({}) ...", size); + let mut reply = vec![0 as u8; size]; + println!("receiving tcp reply ..."); + let _ = self.tcp_receive(&mut reply); + println!("received tcp reply ..."); + Ok(()) + } + + fn mqtt_connect(&mut self, device_id: &str) -> std::result::Result<(), anyhow::Error> { + let mut buf = Vec::new(); + let mut conn = ConnectPacket::new(device_id); + conn.set_clean_session(true); + conn.set_keep_alive(0); + let _ = conn.encode(&mut buf)?; + let _ = self.tcp_send(&mut buf)?; + + thread::sleep(Duration::from_millis(2000)); + drop(buf); + + let _ = self.mqtt_receive_reply()?; + Ok(()) + } + + fn mqtt_publish(&mut self, _device_id: &str, message: &str) -> std::result::Result<(), anyhow::Error> { + println!("entered mqtt publish ..."); + let mut buf = Vec::new(); + let packet = PublishPacket::new( + TopicName::new(format!("bajsevi/location")).unwrap(), + QoSWithPacketIdentifier::Level0, + message.as_bytes(), + ); + println!("created mqtt publish packet ..."); + let _ = packet.encode(&mut buf)?; + println!("modem tcp send publish pakage ..."); + let _ = self.tcp_send(&mut buf)?; + + thread::sleep(Duration::from_millis(2000)); + drop(buf); + + println!("receiving modem publish reply ..."); + let _ = self.mqtt_receive_reply()?; + Ok(()) + } +} + +pub enum Msg { + Location(String), +// Movement(String), +} + +pub fn main( + rx: esp_idf_hal::gpio::Gpio26, + tx: esp_idf_hal::gpio::Gpio27, + uart: serial::UART1, + pwrkey: esp_idf_hal::gpio::Gpio4, + rst: esp_idf_hal::gpio::Gpio5, + power: esp_idf_hal::gpio::Gpio23, + receiver: Receiver, +) -> std::result::Result<(), anyhow::Error> { + let serial_pins = serial::Pins { + tx, + rx, + cts: None, + rts: None, + }; + + // Create the serial and panic with a message ... if we can't create the serial port, then we + // can't communicate with the sim800l module, hence we don't run anymore. + let serial: serial::Serial = serial::Serial::new( + uart, + serial_pins, + serial::config::Config::default().baudrate(Hertz(115200)), + )?; + + let (tx, rx) = serial.split(); + let mut mdm = Modem::new(tx, rx); + + mdm.init(pwrkey, rst, power)?; + + if !mdm.is_gprs_attached()? { + let _ = mdm.gprs_attach_ap( + crate::config::A1_GPRS_AP.apn, + crate::config::A1_GPRS_AP.username, + crate::config::A1_GPRS_AP.password, + )?; + } + + thread::sleep(Duration::from_millis(1000)); + + //println!("setting up client TLS cert"); + //let client_cert = include_bytes!("../certs/full-bin.p12"); + //let client_cert_path = "C:\\USER\\fullchain.pem"; + + //let _ = mdm.upload_cert(client_cert_path, client_cert)?; + //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; + //let _ = mdm.fs_list("C:\\USER\\")?; + + let mut retries = 0; + let is_connected: bool = loop { + if mdm.is_gprs_attached()? { + let _ = mdm.gprs_connect()?; + thread::sleep(Duration::from_millis(1000)); + let ip_addr = mdm.gprs_status()?; + if ip_addr.contains("0.0.0.0") && retries < 5 { + thread::sleep(Duration::from_millis(1000)); + retries += 1; + continue + } else if retries < 5 { + break true + } else { + break false + } + } + }; + + if is_connected { + //println!("connecting to server!"); + //if !mdm.tcp_is_ssl_enabled()? { + // let _ = mdm.tcp_ssl_enable()?; + //} + //if mdm.tcp_is_ssl_enabled()? { + // let _ = mdm.tcp_ssl_disable()?; + //} + let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; + + let _ = mdm.tcp_set_quick_mode(false); + let _ = mdm.tcp_set_manual_receive(true); + let _ = mdm.tcp_connect("51.158.66.64", 1883)?; + + let _ = mdm.mqtt_connect(device_id)?; + + println!("entering queue receive loop ..."); + while let Ok(Msg::Location(msg)) = receiver.recv() { + println!("received message {} | sending to mqtt ...", msg); + let _ = mdm.mqtt_publish(device_id, &msg)?; + } + + + let _ = mdm.tcp_close_connection()?; + } + + Ok(()) } From df46a56cc37ee5c9d6fecf4161e45f384eebc6d7 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 11 Jul 2022 16:16:30 +0200 Subject: [PATCH 05/36] send Movement from accel main thread --- src/accel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accel.rs b/src/accel.rs index 772ed41..5eef823 100644 --- a/src/accel.rs +++ b/src/accel.rs @@ -10,7 +10,7 @@ pub fn main(sender: SyncSender) -> Result<(), anyhow::Error> { println!("entering ACCELERATOR sender loop ..."); for i in 0..20 { println!("sending ACCELERATOR message ({}) of 20 ...", i); - let _ = sender.send(Msg::Location("{\"velocity\": 21.43, \"altitude\": 367}".to_string()))?; + let _ = sender.send(Msg::Movement("{\"velocity\": 21.43, \"altitude\": 367}".to_string()))?; thread::sleep(Duration::from_millis(2000)); } Ok(()) From 1fc1218ba6cb8d56b73ba6b128d6e5c6a39b29d9 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 11 Jul 2022 16:17:17 +0200 Subject: [PATCH 06/36] initial gps read/write and publish --- src/gps.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/gps.rs b/src/gps.rs index 9a2460f..8bf4812 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -4,9 +4,49 @@ use std::sync::mpsc::SyncSender; use std::thread; use std::time::Duration; +use esp_idf_hal::prelude::*; +use esp_idf_hal::serial; + +use ublox::*; + use crate::modem::Msg; -pub fn main(sender: SyncSender) -> Result<(), anyhow::Error> { +pub fn main( + rx: esp_idf_hal::gpio::Gpio32, + tx: esp_idf_hal::gpio::Gpio33, + uart: serial::UART2, + sender: SyncSender, +) -> Result<(), anyhow::Error> { + let serial_pins = serial::Pins { + tx, + rx, + cts: None, + rts: None, + }; + let _serial: serial::Serial = serial::Serial::new( + uart, + serial_pins, + serial::config::Config::default().baudrate(Hertz(9600)), + )?; + + let mut parser = Parser::default(); + let mut it = parser.consume(&raw_packet); + + loop { + match it.next() { + Some(Ok(packet)) => { + println!("We've received a &PacketRef, we can handle it ... {:?}", packet); + } + Some(Err(err)) => { + println!("Received a malformed packet {:?}", err); + } + None => { + println!("The internal buffer is now empty"); + break; + } + } + } + println!("entering GPS sender loop ..."); for i in 0..20 { println!("sending GPS message ({}) of 20 ...", i); From 6f90f46b6856decc31d101e96b97f56ec53e84cc Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 11 Jul 2022 20:13:52 +0200 Subject: [PATCH 07/36] move common serial io code to a module closes: #6 closes: #7 --- Cargo.toml | 1 + src/gps.rs | 63 ++++++++++++-------- src/main.rs | 36 +++++------- src/modem.rs | 159 +++++++++++++++++--------------------------------- src/serial.rs | 137 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 243 insertions(+), 153 deletions(-) create mode 100644 src/serial.rs diff --git a/Cargo.toml b/Cargo.toml index 0150732..4ff3999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ esp-idf-hal = "0.37.4" esp-idf-sys = { version = "0.31.5", features = ["binstart", "native"] } mqtt-protocol = "0.11.2" nb = "1.0.0" +ublox = "0.4.2" [build-dependencies] embuild = "0.29" diff --git a/src/gps.rs b/src/gps.rs index 8bf4812..03115c6 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -1,8 +1,11 @@ use anyhow; -use std::sync::mpsc::SyncSender; -use std::thread; -use std::time::Duration; +use std::{ + sync::mpsc::SyncSender, + thread, + time::Duration, + io::Read, +}; use esp_idf_hal::prelude::*; use esp_idf_hal::serial; @@ -10,10 +13,11 @@ use esp_idf_hal::serial; use ublox::*; use crate::modem::Msg; +use crate::serial::SerialIO; pub fn main( - rx: esp_idf_hal::gpio::Gpio32, tx: esp_idf_hal::gpio::Gpio33, + rx: esp_idf_hal::gpio::Gpio32, uart: serial::UART2, sender: SyncSender, ) -> Result<(), anyhow::Error> { @@ -23,35 +27,46 @@ pub fn main( cts: None, rts: None, }; - let _serial: serial::Serial = serial::Serial::new( + let serial: serial::Serial = serial::Serial::new( uart, serial_pins, serial::config::Config::default().baudrate(Hertz(9600)), )?; + let (tx, rx) = serial.split(); + let mut serial_io = SerialIO::new(tx, rx); let mut parser = Parser::default(); - let mut it = parser.consume(&raw_packet); - - loop { - match it.next() { - Some(Ok(packet)) => { - println!("We've received a &PacketRef, we can handle it ... {:?}", packet); - } - Some(Err(err)) => { - println!("Received a malformed packet {:?}", err); - } - None => { - println!("The internal buffer is now empty"); - break; - } - } - } println!("entering GPS sender loop ..."); - for i in 0..20 { - println!("sending GPS message ({}) of 20 ...", i); - let _ = sender.send(Msg::Location("{\"lat\": 20.4322, \"long\": 44.5432}".to_string()))?; + loop { + let mut local_buf = [0; 100]; + println!("reading 100 bytes from serial ..."); + let nbytes = serial_io.read(&mut local_buf)?; + println!("READ: {}", local_buf.iter().map(|&b| char::from(b)).collect::()); + if nbytes == 0 { + println!("received 0 bytes, exiting ..."); + break; + } + let mut it = parser.consume(&local_buf); + + loop { + match it.next() { + Some(Ok(packet)) => { + let msg = format!("We've received a &PacketRef, we can handle it ... {:?}", packet); + println!("{}", msg); + sender.send(Msg::Location(msg))?; + } + Some(Err(err)) => { + println!("Received a malformed packet {:?}", err); + } + None => { + println!("The internal buffer is now empty"); + break; + } + } + } thread::sleep(Duration::from_millis(2000)); } + println!("exiting GPS sender loop :)"); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 08d6e94..c5fbf50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,19 @@ mod accel; mod config; -mod gps; -mod modem; #[allow(dead_code)] mod command; +mod gps; +mod modem; +mod serial; use anyhow; -use std::thread::{self, JoinHandle}; -use std::cell::RefCell; -use std::time::Duration; +use std::{ + thread::{self, JoinHandle}, + time::Duration, +}; use esp_idf_hal::peripherals::Peripherals; -thread_local! { - static TLS: RefCell = RefCell::new(13); -} - - fn main() -> anyhow::Result<()> { esp_idf_sys::link_patches(); @@ -36,24 +33,19 @@ fn main() -> anyhow::Result<()> { let mut threads: Vec>> = vec![]; - println!("Rust main thread: {:?}", thread::current()); + // Rx/Tx pins for the GPS modem + let gps_rx = dp.pins.gpio32; + let gps_tx = dp.pins.gpio33; + // UART interface for the GPS modem + let gps_uart = dp.uart2; - TLS.with(|tls| { - println!("Main TLS before change: {}", *tls.borrow()); - }); - - TLS.with(|tls| *tls.borrow_mut() = 42); - - TLS.with(|tls| { - println!("Main TLS after change: {}", *tls.borrow()); - }); - let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); let accel_sender = gps_sender.clone(); - threads.push(thread::spawn(move || gps::main(gps_sender))); + let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; + // threads.push(thread::spawn(move || gps::main(gps_rx, gps_tx, gps_uart, gps_sender))); thread::sleep(Duration::from_millis(1000)); threads.push(thread::spawn(move || accel::main(accel_sender))); thread::sleep(Duration::from_millis(1000)); diff --git a/src/modem.rs b/src/modem.rs index b9a59d8..d209211 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -1,15 +1,18 @@ use crate::command::Command; +use crate::serial::SerialIO; use anyhow; -use std::thread; -use std::error::Error; -use std::time::{Duration, Instant}; -use std::sync::mpsc::Receiver; +use std::{ + error::Error, + io::{Read, Write}, + thread, + time::{Duration, Instant}, + sync::mpsc::Receiver, +}; use esp_idf_hal::prelude::*; use esp_idf_hal::serial::{self, Rx, Tx}; -use embedded_hal::serial::{Read, Write}; use embedded_hal::digital::v2::OutputPin; use mqtt::packet::{ConnectPacket, PublishPacket, QoSWithPacketIdentifier}; @@ -20,8 +23,7 @@ const MAX_TCP_MANUAL_REPLY_SIZE: usize = 300; pub type Result = std::result::Result; pub struct Modem { - rx: RxIter, - tx: Tx, + serial: SerialIO, } #[derive(Debug)] @@ -41,74 +43,10 @@ impl std::fmt::Display for ModemError { } } -pub struct RxIter { - inner: Rx, - timeout: Duration, -} - -impl RxIter { - fn reset(&mut self, timeout: Duration) -> &mut Self { - self.timeout = timeout; - self - } - - fn clear(&mut self) -> () { - println!("clearing serial rx"); - self.reset(Duration::from_millis(500)).for_each(drop); - } - - /// Reads a whole line (that ends with \\n) within the given `timeout` passed on input. - fn read_line(&mut self, timeout: Duration) -> Result { - let mut line: String = self.reset(timeout) - .map(|b| char::from(b)) - .take_while(|c| *c != '\n') - .collect(); - - // \r must come right before \n on read; take_while excludes the matched element. - if line.ends_with('\r') { - line.push('\n'); - Ok(line) - } - else if self.timeout.as_millis() == 0 { - Err(ModemError::TimeoutError) - } - else { - Err(ModemError::ReadError) - } - } -} - -impl Iterator for RxIter { - type Item = u8; - - /// `nb` returns Ok(byte), or one of Err(WouldBlock) and Err(Other) which isn't of anyone's - /// interest, so the retry mechanism is triggered on _any_ error every 200ms until a byte is - /// received, or the timeout is reached. - fn next(&mut self) -> Option { - let start = Instant::now(); - loop { - match self.inner.read() { - Ok(b) => { - self.timeout = self.timeout.saturating_sub(start.elapsed()); - break Some(b) - }, - Err(_) => { - if start.elapsed() > self.timeout { - self.timeout = Duration::ZERO; - break None - } - thread::sleep(Duration::from_millis(200)); - } - } - } - } -} - impl Modem { pub fn new(tx: Tx, rx: Rx) -> Self { Self { - rx: RxIter { inner: rx, timeout: Duration::from_millis(0) }, - tx, + serial: SerialIO::new(tx, rx), } } @@ -158,7 +96,7 @@ impl Modem { loop { let timeout = timeout.saturating_sub(start.elapsed()); - let line = self.rx.read_line(timeout)?; + let line = self.serial.read_line(timeout).map_err(|_| ModemError::ReadError)?; print!("Read {} bytes from serial: {}", line.len(), line); response.push_str(&line); if line.contains("ERROR") || line.contains(&match_text) { @@ -169,36 +107,31 @@ impl Modem { } } - #[inline(always)] - fn send_bytes(&mut self, payload: &[u8], eos: Option) -> Result<()> { - //self.rx.clear(); - for b in payload.iter() { - nb::block!(self.tx.write(*b)) - .map_err(|_| ModemError::CommandError(format!("error writing {} to serial", b)))?; - } - eos.map(|b| nb::block!(self.tx.write(b))); - Ok(()) - } - fn send_command(&mut self, cmd: Command) -> Result { println!("-----------------------------------------------------------"); println!("Sending {} ...", cmd.text); - let _ = self.send_bytes(cmd.text.as_bytes(), Some('\r' as u8))?; + let _ = self.serial + .send_bytes(cmd.text.as_bytes(), Some('\r' as u8)) + .map_err(|_| ModemError::SendDataError)?; self.read_response(cmd.contains, cmd.timeout) } fn tcp_send_data(&mut self, buf: &[u8]) -> Result { - let _ = self.send_bytes("AT+CIPSEND".as_bytes(), Some('\r' as u8))?; - let send_request: String = self.rx.reset(Duration::from_millis(3000)) + let _ = self.serial + .write("AT+CIPSEND\r".as_bytes()) + .map_err(|_| ModemError::SendDataError)?; + + let send_prompt: String = self.serial.rx.reset(Duration::from_millis(3000)) .map(char::from) .take_while(|c| *c != '>').collect(); - if send_request != "\r\n" { - println!("{:?}", send_request.as_bytes()); + if send_prompt != "\r\n" { + println!("{:?}", send_prompt.as_bytes()); return Err(ModemError::SendDataError); } - - self.send_bytes(buf, Some(26))?; // 26_u8 = Ctrl+z - to end sending data + self.serial + .send_bytes(buf, Some(26)) // 26_u8 = Ctrl+z - to end sending data + .map_err(|_| ModemError::SendDataError)?; self.read_response(Some("SEND OK".to_string()), Duration::from_millis(3000)) } @@ -321,7 +254,7 @@ impl Modem { let _ = self.send_command(Command::http_set_content("application/json")); let _ = self.send_command(Command::http_set_ssl(true)); let _ = self.send_command(Command::http_post_len(content.len(), 100000)); - let _ = self.send_bytes(content, Some(26)); + let _ = self.serial.send_bytes(content, Some(26)); let _ = self.send_command(Command::http_post()); self.send_command(Command::http_read_response()) } @@ -362,16 +295,20 @@ impl Modem { fn file_write(&mut self, buf: &[u8], path: &str, append: bool, input_time_sec: usize) -> Result<()> { let cmd = Command::fs_file_write(path, append, buf.len(), input_time_sec); - let _ = self.send_bytes(cmd.text.as_bytes(), Some('\r' as u8))?; - let send_request: String = self.rx.reset(Duration::from_millis(3000)) + let _ = self.serial + .send_bytes(cmd.text.as_bytes(), Some('\r' as u8)) + .map_err(|_| ModemError::SendDataError)?; + let send_prompt: String = self.serial.rx.reset(Duration::from_millis(3000)) .map(char::from) .take_while(|c| *c != '>').collect(); - if send_request == "" { + if send_prompt == "" { return Err(ModemError::SendDataError); } - self.send_bytes(buf, None)?; + self.serial + .send_bytes(buf, None) + .map_err(|_| ModemError::SendDataError)?; let _ = self.read_response(Some("OK".to_string()), Duration::from_millis(3000)); Ok(()) @@ -451,9 +388,27 @@ impl Modem { } } +impl std::io::Read for Modem { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.tcp_receive(buf).map_err(|_| std::io::Error::from(std::io::ErrorKind::ConnectionAborted)) + } +} + +impl std::io::Write for Modem { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.tcp_send(buf) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::ConnectionAborted))?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + pub enum Msg { Location(String), -// Movement(String), + Movement(String), } pub fn main( @@ -472,8 +427,6 @@ pub fn main( rts: None, }; - // Create the serial and panic with a message ... if we can't create the serial port, then we - // can't communicate with the sim800l module, hence we don't run anymore. let serial: serial::Serial = serial::Serial::new( uart, serial_pins, @@ -522,13 +475,6 @@ pub fn main( }; if is_connected { - //println!("connecting to server!"); - //if !mdm.tcp_is_ssl_enabled()? { - // let _ = mdm.tcp_ssl_enable()?; - //} - //if mdm.tcp_is_ssl_enabled()? { - // let _ = mdm.tcp_ssl_disable()?; - //} let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; let _ = mdm.tcp_set_quick_mode(false); @@ -543,7 +489,6 @@ pub fn main( let _ = mdm.mqtt_publish(device_id, &msg)?; } - let _ = mdm.tcp_close_connection()?; } diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..1fbc629 --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,137 @@ +use std::error::Error; +use std::io; +use std::thread; +use std::time::{Duration, Instant}; +use embedded_hal::serial::{Read, Write}; +use esp_idf_hal::serial::{self, Rx, Tx}; + +#[derive(Debug)] +pub enum SerialError { + ReadError, + WriteError(String), + TimeoutError, +} + +impl Error for SerialError {} + +impl std::fmt::Display for SerialError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +pub type Result = std::result::Result; + +pub struct RxIter { + inner: Rx, + timeout: Duration, +} + +impl RxIter { + pub fn reset(&mut self, timeout: Duration) -> &mut Self { + self.timeout = timeout; + self + } + + fn clear(&mut self) -> () { + println!("clearing serial rx"); + self.reset(Duration::from_millis(500)).for_each(drop); + } +} + +impl Iterator for RxIter { + type Item = u8; + + /// `nb` returns Ok(byte), or one of Err(WouldBlock) and Err(Other) which isn't of anyone's + /// interest, so the retry mechanism is triggered on _any_ error every 200ms until a byte is + /// received, or the timeout is reached. + fn next(&mut self) -> Option { + let start = Instant::now(); + loop { + match self.inner.read() { + Ok(b) => { + self.timeout = self.timeout.saturating_sub(start.elapsed()); + break Some(b) + }, + Err(_) => { + if start.elapsed() > self.timeout { + self.timeout = Duration::ZERO; + break None + } + thread::sleep(Duration::from_millis(200)); + } + } + } + } +} + +pub struct SerialIO { + pub rx: RxIter, + pub tx: Tx, +} + +impl SerialIO { + pub fn new(tx: Tx, rx: Rx) -> Self { + Self { + rx: RxIter { inner: rx, timeout: Duration::from_millis(0) }, + tx, + } + } + + pub fn send_bytes(&mut self, payload: &[u8], eos: Option) -> Result { + //self.rx.clear(); + for b in payload.iter() { + nb::block!(self.tx.write(*b)) + .map_err(|err| SerialError::WriteError(format!("Error writing '{}' to serial, Original error {}", b, err)))?; + } + eos.map(|b| nb::block!(self.tx.write(b))); + Ok(payload.len() + if eos.is_none() { 0 } else { 1 }) + } + + /// Reads a whole line (that ends with \\n) within the given `timeout` passed on input. + pub fn read_line(&mut self, timeout: Duration) -> Result { + let mut line: String = self.rx.reset(timeout) + .map(|b| char::from(b)) + .take_while(|c| *c != '\n') + .collect(); + + // \r must come right before \n on read; take_while excludes the matched element. + if line.ends_with('\r') { + line.push('\n'); + Ok(line) + } + else if self.rx.timeout.as_millis() == 0 { + Err(SerialError::TimeoutError) + } + else { + Err(SerialError::ReadError) + } + } +} + +impl io::Read for SerialIO { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let buf_size = buf.len(); + let count = self.rx.reset(Duration::from_millis(1000)) + .enumerate() + .map(|(i, b)| { + buf[i] = b; + i + }) + .take_while(|i| i < &buf_size) + .count(); + Ok(count) + } +} + +impl io::Write for SerialIO { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.send_bytes(buf, None) + .map_err(|_| io::Error::from(io::ErrorKind::Other)) + } + + fn flush(&mut self) -> io::Result<()> { + self.tx.flush() + .map_err(|err| io::Error::from(io::ErrorKind::Other)) + } +} From 545fa95f17f2146b61e2cb223502bd3e3bc7fcb3 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Tue, 12 Jul 2022 19:10:04 +0200 Subject: [PATCH 08/36] copy-paste ublox serial config and reading --- Cargo.toml | 2 +- sdkconfig.defaults | 3 + src/gps.rs | 189 ++++++++++++++++++++++++++++++++++++--------- src/main.rs | 14 ++-- src/serial.rs | 2 +- 5 files changed, 165 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ff3999..5e56188 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ esp-idf-hal = "0.37.4" esp-idf-sys = { version = "0.31.5", features = ["binstart", "native"] } mqtt-protocol = "0.11.2" nb = "1.0.0" -ublox = "0.4.2" +ublox = { git = "https://github.com/lkolbly/ublox.git", branch = "master" } [build-dependencies] embuild = "0.29" diff --git a/sdkconfig.defaults b/sdkconfig.defaults index a0f1158..cd6dab8 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -5,6 +5,9 @@ CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000 # This allows to use 1 ms granuality for thread sleeps (10 ms by default). #CONFIG_FREERTOS_HZ=1000 +# Explicitly specify UART0 for console debugging. +CONFIG_CONSOLE_UART_NUM=0 + # Workaround for https://github.com/espressif/esp-idf/issues/7631 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n diff --git a/src/gps.rs b/src/gps.rs index 03115c6..4d356f4 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -3,21 +3,90 @@ use anyhow; use std::{ sync::mpsc::SyncSender, thread, - time::Duration, - io::Read, + time::{Duration, Instant}, + io::{Read, Write}, }; use esp_idf_hal::prelude::*; use esp_idf_hal::serial; +use esp_idf_hal::gpio::*; use ublox::*; use crate::modem::Msg; use crate::serial::SerialIO; +struct Device { + port: SerialIO, + parser: Parser>, +} + +impl Device { + pub fn new(port: serial::Serial, Gpio13>) -> Device { + let parser = Parser::default(); + let (tx, rx) = port.split(); + Device { port: SerialIO::new(tx, rx), parser } + } + + pub fn write_all(&mut self, data: &[u8]) -> std::io::Result<()> { + println!("WRITE: {:?}", data); + self.port.write(data).map(|_| ()) + } + + pub fn update(&mut self, mut cb: T) -> std::io::Result<()> { + println!("UPDATING ... ... ..."); + loop { + let mut local_buf = [0; 1024]; + let nbytes = self.read_port(&mut local_buf)?; + if nbytes == 0 { + break; + } + // parser.consume adds the buffer to its internal buffer, and + // returns an iterator-like object we can use to process the packets + let mut it = self.parser.consume(&local_buf[..nbytes]); + while let Some(Ok(packet)) = it.next() { + println!("READ: {:?}", packet); + cb(packet); + } + } + Ok(()) + } + + pub fn wait_for_ack(&mut self, timeout: Duration) -> std::io::Result<()> { + let mut found_packet = false; + println!("LOOKING FOR ACK ..."); + let start = Instant::now(); + while !found_packet && start.elapsed() < timeout { + self.update(|packet| { + if let PacketRef::AckAck(ack) = packet { + if ack.class() == T::CLASS && ack.msg_id() == T::ID { + println!("FOUND PACKET: {} {}", ack.class(), ack.msg_id()); + found_packet = true; + } + } + })?; + } + Ok(()) + } + + /// Reads the serial port, converting timeouts into "no data received" + fn read_port(&mut self, output: &mut [u8]) -> std::io::Result { + match self.port.read(output) { + Ok(b) => Ok(b), + Err(e) => { + if e.kind() == std::io::ErrorKind::TimedOut { + Ok(0) + } else { + Err(e) + } + } + } + } +} + pub fn main( - tx: esp_idf_hal::gpio::Gpio33, - rx: esp_idf_hal::gpio::Gpio32, + tx: esp_idf_hal::gpio::Gpio12, + rx: esp_idf_hal::gpio::Gpio13, uart: serial::UART2, sender: SyncSender, ) -> Result<(), anyhow::Error> { @@ -27,46 +96,94 @@ pub fn main( cts: None, rts: None, }; + + let serial_config = serial::config::Config::default() + .baudrate(Hertz(9600)) + .data_bits(serial::config::DataBits::DataBits8) + .parity_none() + .flow_control(serial::config::FlowControl::None) + .stop_bits(serial::config::StopBits::STOP1); + let serial: serial::Serial = serial::Serial::new( uart, serial_pins, - serial::config::Config::default().baudrate(Hertz(9600)), + serial_config, )?; - let (tx, rx) = serial.split(); - let mut serial_io = SerialIO::new(tx, rx); - let mut parser = Parser::default(); + let mut device = Device::new(serial); - println!("entering GPS sender loop ..."); - loop { - let mut local_buf = [0; 100]; - println!("reading 100 bytes from serial ..."); - let nbytes = serial_io.read(&mut local_buf)?; - println!("READ: {}", local_buf.iter().map(|&b| char::from(b)).collect::()); - if nbytes == 0 { - println!("received 0 bytes, exiting ..."); - break; - } - let mut it = parser.consume(&local_buf); - - loop { - match it.next() { - Some(Ok(packet)) => { - let msg = format!("We've received a &PacketRef, we can handle it ... {:?}", packet); - println!("{}", msg); - sender.send(Msg::Location(msg))?; - } - Some(Err(err)) => { - println!("Received a malformed packet {:?}", err); - } - None => { - println!("The internal buffer is now empty"); - break; - } + // Configure the device to talk UBX + device + .write_all( + &CfgPrtUartBuilder { + portid: UartPortId::Uart1, + reserved0: 0, + tx_ready: 0, + mode: UartMode::new(DataBits::Eight, Parity::None, StopBits::One), + baud_rate: 9600, + in_proto_mask: InProtoMask::all(), + out_proto_mask: OutProtoMask::UBLOX, + flags: 0, + reserved5: 0, } - } - thread::sleep(Duration::from_millis(2000)); + .into_packet_bytes(), + ) + .unwrap(); + device.wait_for_ack::(Duration::from_millis(3000)).unwrap(); + println!("CfgPrtUart acked!"); + + // Enable the NavPosVelTime packet + device + .write_all( + &CfgMsgAllPortsBuilder::set_rate_for::([0, 1, 0, 0, 0, 0]) + .into_packet_bytes(), + ) + .unwrap(); + device.wait_for_ack::(Duration::from_millis(3000)).unwrap(); + println!("CfgMsgAllPorts acked!"); + + // Send a packet request for the MonVer packet + device + .write_all(&UbxPacketRequest::request_for::().into_packet_bytes()) + .unwrap(); + + // Start reading data + println!("Opened u-blox device, waiting for solutions..."); + for _ in 0..20 { + device + .update(|packet| match packet { + PacketRef::MonVer(packet) => { + println!( + "SW version: {} HW version: {}", + packet.software_version(), + packet.hardware_version() + ); + println!("{:?}", packet); + } + PacketRef::NavPosVelTime(sol) => { + let has_posvel = sol.fix_type() == GpsFix::Fix3D + || sol.fix_type() == GpsFix::GPSPlusDeadReckoning; + + if has_posvel { + let pos: Position = (&sol).into(); + let vel: Velocity = (&sol).into(); + println!( + "Latitude: {:.5} Longitude: {:.5} Altitude: {:.2}m", + pos.lat, pos.lon, pos.alt + ); + println!( + "Speed: {:.2} m/s Heading: {:.2} degrees", + vel.speed, vel.heading + ); + println!("Sol: {:?}", sol); + } + } + _ => { + println!("{:?}", packet); + } + })? } + println!("exiting GPS sender loop :)"); Ok(()) } diff --git a/src/main.rs b/src/main.rs index c5fbf50..3265b58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,11 +31,11 @@ fn main() -> anyhow::Result<()> { // UART interface for the GSM modem let modem_uart = dp.uart1; - let mut threads: Vec>> = vec![]; + // let mut threads: Vec>> = vec![]; // Rx/Tx pins for the GPS modem - let gps_rx = dp.pins.gpio32; - let gps_tx = dp.pins.gpio33; + let gps_rx = dp.pins.gpio13; + let gps_tx = dp.pins.gpio12; // UART interface for the GPS modem let gps_uart = dp.uart2; @@ -46,11 +46,11 @@ fn main() -> anyhow::Result<()> { let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; // threads.push(thread::spawn(move || gps::main(gps_rx, gps_tx, gps_uart, gps_sender))); - thread::sleep(Duration::from_millis(1000)); - threads.push(thread::spawn(move || accel::main(accel_sender))); - thread::sleep(Duration::from_millis(1000)); + //thread::sleep(Duration::from_millis(1000)); + //threads.push(thread::spawn(move || accel::main(accel_sender))); + //thread::sleep(Duration::from_millis(1000)); - let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; + //let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; Ok(()) } diff --git a/src/serial.rs b/src/serial.rs index 1fbc629..fcc9aeb 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -132,6 +132,6 @@ impl io::Write for SerialIO { fn flush(&mut self) -> io::Result<()> { self.tx.flush() - .map_err(|err| io::Error::from(io::ErrorKind::Other)) + .map_err(|_| io::Error::from(io::ErrorKind::Other)) } } From a01b6d3a7b78871caee7ed7d7788766906b511e6 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Tue, 12 Jul 2022 19:11:29 +0200 Subject: [PATCH 09/36] decrease gps rate --- src/gps.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps.rs b/src/gps.rs index 4d356f4..717c605 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -135,7 +135,7 @@ pub fn main( // Enable the NavPosVelTime packet device .write_all( - &CfgMsgAllPortsBuilder::set_rate_for::([0, 1, 0, 0, 0, 0]) + &CfgMsgAllPortsBuilder::set_rate_for::([0, 0, 1, 0, 0, 0]) .into_packet_bytes(), ) .unwrap(); From db3cd1548e34ffb478dabb751b7689ee79366577 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Wed, 20 Jul 2022 12:20:17 +0200 Subject: [PATCH 10/36] match payload v.s. sent bytes length in Serial::send_bytes --- src/serial.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/serial.rs b/src/serial.rs index fcc9aeb..817fb67 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -8,7 +8,7 @@ use esp_idf_hal::serial::{self, Rx, Tx}; #[derive(Debug)] pub enum SerialError { ReadError, - WriteError(String), + WriteError, TimeoutError, } @@ -79,13 +79,19 @@ impl SerialIO { } pub fn send_bytes(&mut self, payload: &[u8], eos: Option) -> Result { - //self.rx.clear(); + let mut num_bytes = 0; for b in payload.iter() { nb::block!(self.tx.write(*b)) - .map_err(|err| SerialError::WriteError(format!("Error writing '{}' to serial, Original error {}", b, err)))?; + .map_err(|err| SerialError::WriteError)?; + num_bytes += 1; + } + if num_bytes == payload.len() { + eos.map(|b| nb::block!(self.tx.write(b))); + Ok(num_bytes + if eos.is_none() { 0 } else { 1 }) + } + else { + Err(SerialError::WriteError) } - eos.map(|b| nb::block!(self.tx.write(b))); - Ok(payload.len() + if eos.is_none() { 0 } else { 1 }) } /// Reads a whole line (that ends with \\n) within the given `timeout` passed on input. From 63359cc4c08483d4cc39c77f7a8b427ad6ac46a8 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Wed, 20 Jul 2022 12:27:42 +0200 Subject: [PATCH 11/36] TODO: handle reading lines in a bettwr way --- src/serial.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/serial.rs b/src/serial.rs index 817fb67..79c6b12 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -101,6 +101,8 @@ impl SerialIO { .take_while(|c| *c != '\n') .collect(); + // TODO: \r\n is true for sim800l, but might not be valud for other devices. Re-implement + // this function so that it can be used on all devices. // \r must come right before \n on read; take_while excludes the matched element. if line.ends_with('\r') { line.push('\n'); From 23ff182b651f753685d60616a6efc0fe7e4c2310 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Wed, 20 Jul 2022 12:33:11 +0200 Subject: [PATCH 12/36] use same interface in gps as in gsm modem --- src/gps.rs | 58 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/gps.rs b/src/gps.rs index 717c605..ef3f90a 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -8,24 +8,22 @@ use std::{ }; use esp_idf_hal::prelude::*; -use esp_idf_hal::serial; -use esp_idf_hal::gpio::*; +use esp_idf_hal::serial::{self, Rx, Tx}; use ublox::*; use crate::modem::Msg; use crate::serial::SerialIO; -struct Device { +struct GpsModule { port: SerialIO, parser: Parser>, } -impl Device { - pub fn new(port: serial::Serial, Gpio13>) -> Device { +impl GpsModule { + pub fn new(tx: Tx, rx: Rx) -> Self { let parser = Parser::default(); - let (tx, rx) = port.split(); - Device { port: SerialIO::new(tx, rx), parser } + GpsModule { port: SerialIO::new(tx, rx), parser } } pub fn write_all(&mut self, data: &[u8]) -> std::io::Result<()> { @@ -39,6 +37,7 @@ impl Device { let mut local_buf = [0; 1024]; let nbytes = self.read_port(&mut local_buf)?; if nbytes == 0 { + println!("no bytes to read :("); break; } // parser.consume adds the buffer to its internal buffer, and @@ -52,7 +51,7 @@ impl Device { Ok(()) } - pub fn wait_for_ack(&mut self, timeout: Duration) -> std::io::Result<()> { + pub fn wait_for_ack(&mut self, timeout: Duration) -> std::io::Result { let mut found_packet = false; println!("LOOKING FOR ACK ..."); let start = Instant::now(); @@ -64,9 +63,13 @@ impl Device { found_packet = true; } } + else if let PacketRef::AckNak(nak) = packet { + println!("NAK PACKET: {} {}", nak.class(), nak.msg_id()); + } })?; } - Ok(()) + println!("exiting wait_for_ack"); + Ok(found_packet) } /// Reads the serial port, converting timeouts into "no data received" @@ -110,7 +113,8 @@ pub fn main( serial_config, )?; - let mut device = Device::new(serial); + let (tx, rx) = serial.split(); + let mut device = GpsModule::new(tx, rx); // Configure the device to talk UBX device @@ -127,25 +131,30 @@ pub fn main( reserved5: 0, } .into_packet_bytes(), - ) - .unwrap(); + )?; device.wait_for_ack::(Duration::from_millis(3000)).unwrap(); println!("CfgPrtUart acked!"); - // Enable the NavPosVelTime packet - device - .write_all( - &CfgMsgAllPortsBuilder::set_rate_for::([0, 0, 1, 0, 0, 0]) + // Set interval for the NavPosVelTime packet + println!("Sending set_rate_for:: ..."); + for i in 1..5 { + device + .write_all( + &CfgMsgAllPortsBuilder::set_rate_for::([0, 1, 1, 0, 0, 0]) .into_packet_bytes(), - ) - .unwrap(); - device.wait_for_ack::(Duration::from_millis(3000)).unwrap(); - println!("CfgMsgAllPorts acked!"); + ) + .unwrap(); + println!("SENT set_rate_for::({}) !!!", i); + if let Ok(true) = device.wait_for_ack::(Duration::from_millis(3000)) { + println!("Setting rate for NavPosVelTime acked! Exiting loop ..."); + break + } + } // Send a packet request for the MonVer packet - device - .write_all(&UbxPacketRequest::request_for::().into_packet_bytes()) - .unwrap(); + //device + // .write_all(&UbxPacketRequest::request_for::().into_packet_bytes()) + // .unwrap(); // Start reading data println!("Opened u-blox device, waiting for solutions..."); @@ -181,7 +190,8 @@ pub fn main( _ => { println!("{:?}", packet); } - })? + })?; + thread::sleep(Duration::from_millis(1000)); } println!("exiting GPS sender loop :)"); From 78df516fba925fd923efa331afeb07aa6e3ba5c4 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Wed, 20 Jul 2022 13:32:13 +0200 Subject: [PATCH 13/36] send received messages via mqtt in modem main --- src/accel.rs | 4 ++-- src/gps.rs | 20 ++++++++++---------- src/main.rs | 4 +++- src/modem.rs | 32 +++++++++++++++++++++++--------- src/types.rs | 14 ++++++++++++++ 5 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 src/types.rs diff --git a/src/accel.rs b/src/accel.rs index 5eef823..50c0e56 100644 --- a/src/accel.rs +++ b/src/accel.rs @@ -4,13 +4,13 @@ use std::sync::mpsc::SyncSender; use std::thread; use std::time::Duration; -use crate::modem::Msg; +use crate::types::*; pub fn main(sender: SyncSender) -> Result<(), anyhow::Error> { println!("entering ACCELERATOR sender loop ..."); for i in 0..20 { println!("sending ACCELERATOR message ({}) of 20 ...", i); - let _ = sender.send(Msg::Movement("{\"velocity\": 21.43, \"altitude\": 367}".to_string()))?; + let _ = sender.send(Msg::Accelerometer("{\"velocity\": 21.43, \"altitude\": 367}".to_string()))?; thread::sleep(Duration::from_millis(2000)); } Ok(()) diff --git a/src/gps.rs b/src/gps.rs index ef3f90a..025cad7 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -12,7 +12,7 @@ use esp_idf_hal::serial::{self, Rx, Tx}; use ublox::*; -use crate::modem::Msg; +use crate::types::*; use crate::serial::SerialIO; struct GpsModule { @@ -176,15 +176,15 @@ pub fn main( if has_posvel { let pos: Position = (&sol).into(); let vel: Velocity = (&sol).into(); - println!( - "Latitude: {:.5} Longitude: {:.5} Altitude: {:.2}m", - pos.lat, pos.lon, pos.alt - ); - println!( - "Speed: {:.2} m/s Heading: {:.2} degrees", - vel.speed, vel.heading - ); - println!("Sol: {:?}", sol); + let solution = Solution { + latitude: pos.lat, + longitude: pos.lon, + altitude: pos.alt, + speed: vel.speed, + direction: vel.heading, + }; + println!("Sol: {:?}", solution); + sender.send(Msg::Gps(solution)); } } _ => { diff --git a/src/main.rs b/src/main.rs index 3265b58..0505ed7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod command; mod gps; mod modem; mod serial; +mod types; use anyhow; use std::{ @@ -13,6 +14,7 @@ use std::{ }; use esp_idf_hal::peripherals::Peripherals; +use types::*; fn main() -> anyhow::Result<()> { esp_idf_sys::link_patches(); @@ -40,7 +42,7 @@ fn main() -> anyhow::Result<()> { let gps_uart = dp.uart2; - let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); + let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); let accel_sender = gps_sender.clone(); diff --git a/src/modem.rs b/src/modem.rs index d209211..9d1e46e 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -1,5 +1,6 @@ use crate::command::Command; use crate::serial::SerialIO; +use crate::types::*; use anyhow; use std::{ @@ -406,11 +407,6 @@ impl std::io::Write for Modem { } } -pub enum Msg { - Location(String), - Movement(String), -} - pub fn main( rx: esp_idf_hal::gpio::Gpio26, tx: esp_idf_hal::gpio::Gpio27, @@ -484,10 +480,28 @@ pub fn main( let _ = mdm.mqtt_connect(device_id)?; println!("entering queue receive loop ..."); - while let Ok(Msg::Location(msg)) = receiver.recv() { - println!("received message {} | sending to mqtt ...", msg); - let _ = mdm.mqtt_publish(device_id, &msg)?; - } + let mut err_count = 0; + loop { + match receiver.recv() { + Ok(Msg::Gps(solution)) => { + println!("received GPS solution {:?} | sending to mqtt ...", solution); + let _ = mdm.mqtt_publish(device_id, &format!("{:?}", solution))?; + }, + Ok(Msg::Accelerometer(acc)) => { + println!("received message {} | sending to mqtt ...", acc); + let _ = mdm.mqtt_publish(device_id, &format!("{:?}", acc))?; + } + Err(e) => { + println!("received error {} | NOT sending to mqtt ...", e); + if err_count < 10 { + err_count += 1; + } + else { + break + } + } + } + }; let _ = mdm.tcp_close_connection()?; } diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..daed150 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,14 @@ +#[derive(Debug)] +pub struct Solution { + pub latitude: f64, + pub longitude: f64, + pub altitude: f64, + pub speed: f64, + pub direction: f64, +} + +pub enum Msg { + Gps(Solution), + Accelerometer(String), +} + From 41e89028e9e00cc6cf20927c79b4f3eed5535ce0 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Thu, 21 Jul 2022 16:24:14 +0200 Subject: [PATCH 14/36] map to () instead of ? ... Ok(()) --- src/modem.rs | 57 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/src/modem.rs b/src/modem.rs index 9d1e46e..93ff631 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -155,9 +155,8 @@ impl Modem { pub fn gprs_connect(&mut self)-> Result<()> { println!("open gprs ..."); - let _ = self.send_command(Command::gprs_bearer_open())?; - - Ok(()) + self.send_command(Command::gprs_bearer_open()) + .map(|_| ()) } pub fn is_gprs_attached(&mut self)-> Result { @@ -171,33 +170,33 @@ impl Modem { } pub fn tcp_ssl_disable(&mut self) -> Result<()> { - let _ = self.send_command(Command::tcp_ssl_set(false))?; - Ok(()) + self.send_command(Command::tcp_ssl_set(false)) + .map(|_| ()) } pub fn tcp_ssl_enable(&mut self) -> Result<()> { - let _ = self.send_command(Command::tcp_ssl_set(true))?; - Ok(()) + self.send_command(Command::tcp_ssl_set(true)) + .map(|_| ()) } pub fn tcp_connect(&mut self, addr: &str, port: u16) -> Result<()> { - self.send_command(Command::tcp_connect(addr, port))?; - Ok(()) + self.send_command(Command::tcp_connect(addr, port)) + .map(|_| ()) } pub fn tcp_set_quick_mode(&mut self, mode: bool) -> Result<()> { - self.send_command(Command::tcp_set_quick_mode(mode))?; - Ok(()) + self.send_command(Command::tcp_set_quick_mode(mode)) + .map(|_| ()) } pub fn tcp_set_manual_receive(&mut self, is_manual: bool) -> Result<()> { - self.send_command(Command::tcp_set_manual_receive(is_manual))?; - Ok(()) + self.send_command(Command::tcp_set_manual_receive(is_manual)) + .map(|_| ()) } pub fn tcp_send(&mut self, buf: &[u8]) -> Result<()> { - self.tcp_send_data(buf)?; - Ok(()) + self.tcp_send_data(buf) + .map(|_| ()) } fn tcp_parse_response_size(&mut self, reply_line: &str) -> Result { @@ -271,8 +270,8 @@ impl Modem { } pub fn http_close(&mut self) -> Result<()> { - let _ = self.send_command(Command::http_close())?; - Ok(()) + self.send_command(Command::http_close()) + .map(|_| ()) } pub fn chip_info(&mut self) -> Result<()> { @@ -285,13 +284,13 @@ impl Modem { } pub fn location(&mut self) -> Result<()> { - let _ = self.send_command(Command::get_location())?; - Ok(()) + self.send_command(Command::get_location()) + .map(|_| ()) } pub fn ssl_opt(&mut self) -> Result<()> { - let _ = self.send_command(Command::ssl_opt())?; - Ok(()) + self.send_command(Command::ssl_opt()) + .map(|_| ()) } fn file_write(&mut self, buf: &[u8], path: &str, append: bool, input_time_sec: usize) -> Result<()> { @@ -322,23 +321,23 @@ impl Modem { } pub fn fs_list(&mut self, path: &str) -> Result<()> { - let _ = self.send_command(Command::fs_list(path))?; - Ok(()) + self.send_command(Command::fs_list(path)) + .map(|_| ()) } pub fn fs_free_space(&mut self) -> Result<()> { - let _ = self.send_command(Command::fs_free_size())?; - Ok(()) + self.send_command(Command::fs_free_size()) + .map(|_| ()) } pub fn ssl_set_client_cert(&mut self, path: &str, password: &str) -> Result<()> { - let _ = self.send_command(Command::ssl_set_client_cert(path, password))?; - Ok(()) + self.send_command(Command::ssl_set_client_cert(path, password)) + .map(|_| ()) } pub fn ssl_set_root_cert(&mut self, path: &str, filesize: usize) -> Result<()> { - let _ = self.send_command(Command::ssl_set_root_cert(path, filesize))?; - Ok(()) + self.send_command(Command::ssl_set_root_cert(path, filesize)) + .map(|_| ()) } fn mqtt_receive_reply(&mut self) -> std::result::Result<(), anyhow::Error> { From f7f0689f46ecc6978d2fb54252b751ebbe6e545f Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 8 Aug 2022 12:54:29 +0200 Subject: [PATCH 15/36] add a todo for sim800l response parsing --- src/modem.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modem.rs b/src/modem.rs index 93ff631..35f567f 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -223,6 +223,12 @@ impl Modem { loop { let reply = self.send_command(Command::tcp_receive(MAX_TCP_MANUAL_REPLY_SIZE)) .map(|reply| { + // TODO: parse the response properly + // 1. the first line is \r\n + // 2. next is the +CIPRXGET: 2,X,Y where X is the number of bytes read and Y is + // the number of bytes left to be read + // 3. immediately after this the payload is returned (with size X) + // 4. OK reply .split("\r\n") .filter(|line| line.len() > 2 && !line.contains("+CIPRXGET: 2,")) From e42302988abb6dcc47b13ea63c1dc45ed6725491 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sat, 26 Nov 2022 18:58:30 +0100 Subject: [PATCH 16/36] derive Debug for commands --- src/command.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/command.rs b/src/command.rs index cfebef1..ce95655 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,5 +1,6 @@ use std::time::Duration; +#[derive(Debug)] pub struct Command { pub text: String, pub timeout: Duration, From b6e7e64e729d123c7c683ca24d2b58433be992a0 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sat, 26 Nov 2022 18:59:33 +0100 Subject: [PATCH 17/36] refactor and make mqtt work --- src/main.rs | 9 ++-- src/modem.rs | 149 ++++++++++++++++++++++++++------------------------- 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0505ed7..72c2d8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ fn main() -> anyhow::Result<()> { // UART interface for the GSM modem let modem_uart = dp.uart1; - // let mut threads: Vec>> = vec![]; + let mut threads: Vec>> = vec![]; // Rx/Tx pins for the GPS modem let gps_rx = dp.pins.gpio13; @@ -43,16 +43,15 @@ fn main() -> anyhow::Result<()> { let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); - let accel_sender = gps_sender.clone(); - let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; + // let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; // threads.push(thread::spawn(move || gps::main(gps_rx, gps_tx, gps_uart, gps_sender))); //thread::sleep(Duration::from_millis(1000)); - //threads.push(thread::spawn(move || accel::main(accel_sender))); + threads.push(thread::spawn(move || accel::main(accel_sender))); //thread::sleep(Duration::from_millis(1000)); - //let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; + let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; Ok(()) } diff --git a/src/modem.rs b/src/modem.rs index 35f567f..09141bf 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -31,7 +31,7 @@ pub struct Modem { pub enum ModemError { CommandError(String), SetupError(String), - SendDataError, + SendDataError(String), ReadError, TimeoutError, } @@ -113,26 +113,26 @@ impl Modem { println!("Sending {} ...", cmd.text); let _ = self.serial .send_bytes(cmd.text.as_bytes(), Some('\r' as u8)) - .map_err(|_| ModemError::SendDataError)?; + .map_err(|_| ModemError::SendDataError(format!("Error in send_command({:?})", cmd)))?; self.read_response(cmd.contains, cmd.timeout) } - fn tcp_send_data(&mut self, buf: &[u8]) -> Result { + fn tcp_manual_send_data(&mut self, buf: &[u8]) -> Result { let _ = self.serial .write("AT+CIPSEND\r".as_bytes()) - .map_err(|_| ModemError::SendDataError)?; + .map_err(|_| ModemError::SendDataError("Error in tcp_manual_send_data ... AT_CIPSEND\\r".to_string()))?; let send_prompt: String = self.serial.rx.reset(Duration::from_millis(3000)) .map(char::from) .take_while(|c| *c != '>').collect(); if send_prompt != "\r\n" { - println!("{:?}", send_prompt.as_bytes()); - return Err(ModemError::SendDataError); + let msg = format!("Prompt error, expected \\r\\n, got {:?}", send_prompt.as_bytes()); + return Err(ModemError::SendDataError(msg)); } self.serial - .send_bytes(buf, Some(26)) // 26_u8 = Ctrl+z - to end sending data - .map_err(|_| ModemError::SendDataError)?; + .send_bytes(buf, Some(26_u8)) // 26_u8 = Ctrl+z - to end sending data + .map_err(|err| ModemError::SendDataError(format!("{:?}", err)))?; self.read_response(Some("SEND OK".to_string()), Duration::from_millis(3000)) } @@ -164,6 +164,26 @@ impl Modem { Ok(res.contains("+CGATT: 1")) } + fn try_connect_gprs(&mut self) -> Result<()> { + let mut retries = 0; + loop { + if self.is_gprs_attached()? { + let _ = self.gprs_connect()?; + thread::sleep(Duration::from_millis(1000)); + let ip_addr = self.gprs_status()?; + if ip_addr.contains("0.0.0.0") && retries < 5 { + thread::sleep(Duration::from_millis(1000)); + retries += 1; + continue + } else if retries < 5 { + break Ok(()) + } else { + break Err(ModemError::SetupError(format!("Cannot connect to GPRS after {} retries ... bailing!", retries))); + } + } + } + } + pub fn tcp_is_ssl_enabled(&mut self) -> Result { let res = self.send_command(Command::tcp_ssl_check())?; Ok(res.contains("+CIPSSL: (1)")) @@ -194,8 +214,8 @@ impl Modem { .map(|_| ()) } - pub fn tcp_send(&mut self, buf: &[u8]) -> Result<()> { - self.tcp_send_data(buf) + pub fn tcp_manual_send(&mut self, buf: &[u8]) -> Result<()> { + self.tcp_manual_send_data(buf) .map(|_| ()) } @@ -303,18 +323,18 @@ impl Modem { let cmd = Command::fs_file_write(path, append, buf.len(), input_time_sec); let _ = self.serial .send_bytes(cmd.text.as_bytes(), Some('\r' as u8)) - .map_err(|_| ModemError::SendDataError)?; + .map_err(|err| ModemError::SendDataError(format!("File write error ({:?})", err)))?; let send_prompt: String = self.serial.rx.reset(Duration::from_millis(3000)) .map(char::from) .take_while(|c| *c != '>').collect(); if send_prompt == "" { - return Err(ModemError::SendDataError); + return Err(ModemError::SendDataError("Prompt empty, expected: \\r\\n".to_string())); } self.serial .send_bytes(buf, None) - .map_err(|_| ModemError::SendDataError)?; + .map_err(|err| ModemError::SendDataError(format!("Error sending bytes via serial ({:?})", err)))?; let _ = self.read_response(Some("OK".to_string()), Duration::from_millis(3000)); Ok(()) @@ -347,7 +367,7 @@ impl Modem { } fn mqtt_receive_reply(&mut self) -> std::result::Result<(), anyhow::Error> { - println!("entered receiving modem reply ..."); + println!("receiving mqtt reply from modem ..."); let size = self.tcp_receive_reply_len()?; println!("receiving reply len({}) ...", size); let mut reply = vec![0 as u8; size]; @@ -361,9 +381,9 @@ impl Modem { let mut buf = Vec::new(); let mut conn = ConnectPacket::new(device_id); conn.set_clean_session(true); - conn.set_keep_alive(0); + conn.set_keep_alive(100); let _ = conn.encode(&mut buf)?; - let _ = self.tcp_send(&mut buf)?; + let _ = self.tcp_manual_send(&mut buf)?; thread::sleep(Duration::from_millis(2000)); drop(buf); @@ -383,7 +403,7 @@ impl Modem { println!("created mqtt publish packet ..."); let _ = packet.encode(&mut buf)?; println!("modem tcp send publish pakage ..."); - let _ = self.tcp_send(&mut buf)?; + let _ = self.tcp_manual_send(&mut buf)?; thread::sleep(Duration::from_millis(2000)); drop(buf); @@ -402,7 +422,7 @@ impl std::io::Read for Modem { impl std::io::Write for Modem { fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.tcp_send(buf) + self.tcp_manual_send(buf) .map_err(|_| std::io::Error::from(std::io::ErrorKind::ConnectionAborted))?; Ok(buf.len()) } @@ -439,15 +459,7 @@ pub fn main( mdm.init(pwrkey, rst, power)?; - if !mdm.is_gprs_attached()? { - let _ = mdm.gprs_attach_ap( - crate::config::A1_GPRS_AP.apn, - crate::config::A1_GPRS_AP.username, - crate::config::A1_GPRS_AP.password, - )?; - } - - thread::sleep(Duration::from_millis(1000)); + // thread::sleep(Duration::from_millis(500)); //println!("setting up client TLS cert"); //let client_cert = include_bytes!("../certs/full-bin.p12"); @@ -457,59 +469,50 @@ pub fn main( //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; //let _ = mdm.fs_list("C:\\USER\\")?; - let mut retries = 0; - let is_connected: bool = loop { - if mdm.is_gprs_attached()? { - let _ = mdm.gprs_connect()?; - thread::sleep(Duration::from_millis(1000)); - let ip_addr = mdm.gprs_status()?; - if ip_addr.contains("0.0.0.0") && retries < 5 { - thread::sleep(Duration::from_millis(1000)); - retries += 1; - continue - } else if retries < 5 { - break true - } else { - break false - } + loop { + if !mdm.is_gprs_attached()? { + let _ = mdm.gprs_attach_ap( + crate::config::A1_GPRS_AP.apn, + crate::config::A1_GPRS_AP.username, + crate::config::A1_GPRS_AP.password, + )?; } - }; + if let Ok(()) = mdm.try_connect_gprs() { + let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; - if is_connected { - let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; + let _ = mdm.tcp_set_quick_mode(false); + let _ = mdm.tcp_set_manual_receive(true); + let _ = mdm.tcp_connect("51.158.66.64", 7887)?; - let _ = mdm.tcp_set_quick_mode(false); - let _ = mdm.tcp_set_manual_receive(true); - let _ = mdm.tcp_connect("51.158.66.64", 1883)?; + let _ = mdm.mqtt_connect(device_id)?; - let _ = mdm.mqtt_connect(device_id)?; - - println!("entering queue receive loop ..."); - let mut err_count = 0; - loop { - match receiver.recv() { - Ok(Msg::Gps(solution)) => { - println!("received GPS solution {:?} | sending to mqtt ...", solution); - let _ = mdm.mqtt_publish(device_id, &format!("{:?}", solution))?; - }, - Ok(Msg::Accelerometer(acc)) => { - println!("received message {} | sending to mqtt ...", acc); - let _ = mdm.mqtt_publish(device_id, &format!("{:?}", acc))?; - } - Err(e) => { - println!("received error {} | NOT sending to mqtt ...", e); - if err_count < 10 { - err_count += 1; + println!("entering queue receive loop ..."); + let mut err_count = 0; + let _ = loop { + match receiver.recv() { + Ok(Msg::Gps(solution)) => { + println!("received GPS solution {:?} | sending to mqtt ...", solution); + let _ = mdm.mqtt_publish(device_id, &format!("{:?}", solution))?; + err_count = 0; + }, + Ok(Msg::Accelerometer(acc)) => { + println!("received accel {} | sending to mqtt ...", acc); + let _ = mdm.mqtt_publish(device_id, &format!("{:?}", acc))?; + err_count = 0; } - else { - break + Err(e) => { + if err_count < 10 { + err_count += 1; + println!("received error {} | NOT sending to mqtt ...", e); + } + else { + break + } } } - } - }; + }; - let _ = mdm.tcp_close_connection()?; + let _ = mdm.tcp_close_connection()?; + } } - - Ok(()) } From 018af712625434d624210e146121434ef9843233 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Tue, 29 Nov 2022 15:19:42 +0100 Subject: [PATCH 18/36] make serial read/write non-blocking --- src/modem.rs | 84 ++++++++++++++++++------------- src/serial.rs | 134 ++++++++++++++++++-------------------------------- 2 files changed, 96 insertions(+), 122 deletions(-) diff --git a/src/modem.rs b/src/modem.rs index 09141bf..11cca89 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -7,7 +7,7 @@ use std::{ error::Error, io::{Read, Write}, thread, - time::{Duration, Instant}, + time::Duration, sync::mpsc::Receiver, }; @@ -90,31 +90,52 @@ impl Modem { /// None, then the first line is returned. If a timeout is reached. The timeout is provided on /// input via the `timeout` argument. The first argument `contains` is checked against every /// line in the response. - fn read_response(&mut self, contains: Option, timeout: Duration) -> Result { + fn command_read_response(&mut self) -> Result { let mut response = String::new(); - let start = Instant::now(); - let match_text: String = contains.unwrap_or("\n".to_string()); loop { - let timeout = timeout.saturating_sub(start.elapsed()); - let line = self.serial.read_line(timeout).map_err(|_| ModemError::ReadError)?; - print!("Read {} bytes from serial: {}", line.len(), line); - response.push_str(&line); - if line.contains("ERROR") || line.contains(&match_text) { - println!("Found match {} for line {} ; exiting response reader now ...", match_text, line); - println!("-----------------------------------------------------------"); - break Ok(response.to_string()) + let mut buf = vec![0; 1024]; + let num_bytes = self.serial + .read(buf.as_mut_slice()) + .map_err(|_| ModemError::ReadError)?; + + response.push_str(std::str::from_utf8(&buf[0..num_bytes]).map_err(|_| ModemError::ReadError)?); + + if num_bytes < buf.len() { + break } } + + print!("Read {} bytes from serial: {}", response.len(), response); + Ok(response) } fn send_command(&mut self, cmd: Command) -> Result { println!("-----------------------------------------------------------"); - println!("Sending {} ...", cmd.text); + println!("Sending to TX ({}) ...", cmd.text); + let _ = self.serial - .send_bytes(cmd.text.as_bytes(), Some('\r' as u8)) - .map_err(|_| ModemError::SendDataError(format!("Error in send_command({:?})", cmd)))?; - self.read_response(cmd.contains, cmd.timeout) + .write_bytes(cmd.text.as_bytes()) + .map_err(|_| ModemError::SendDataError(format!("Error in send_command({})", cmd.text)))?; + + let _ = self.serial + .write(&['\r' as u8]) + .map_err(|_| ModemError::SendDataError(format!("Error in send_command({})", cmd.text)))?; + + self.command_read_response() + } + + fn handle_prompt(&mut self) -> Result<()> { + let mut prompt_buf = vec![0; 3]; + let prompt_len = self.serial.read(&mut prompt_buf).map_err(|_| ModemError::ReadError)?; + let prompt = std::str::from_utf8(prompt_buf.as_slice()).unwrap_or(""); + + if prompt_len != 3 && prompt != "\r\n>" { + let msg = format!("Prompt error, expected \\r\\n>, got {:?}", prompt); + Err(ModemError::SendDataError(msg)) + } else { + Ok(()) + } } fn tcp_manual_send_data(&mut self, buf: &[u8]) -> Result { @@ -122,18 +143,15 @@ impl Modem { .write("AT+CIPSEND\r".as_bytes()) .map_err(|_| ModemError::SendDataError("Error in tcp_manual_send_data ... AT_CIPSEND\\r".to_string()))?; - let send_prompt: String = self.serial.rx.reset(Duration::from_millis(3000)) - .map(char::from) - .take_while(|c| *c != '>').collect(); + let _ = self.handle_prompt()?; - if send_prompt != "\r\n" { - let msg = format!("Prompt error, expected \\r\\n, got {:?}", send_prompt.as_bytes()); - return Err(ModemError::SendDataError(msg)); - } self.serial - .send_bytes(buf, Some(26_u8)) // 26_u8 = Ctrl+z - to end sending data + .write_bytes(buf) .map_err(|err| ModemError::SendDataError(format!("{:?}", err)))?; - self.read_response(Some("SEND OK".to_string()), Duration::from_millis(3000)) + self.serial + .write(&[26_u8]) // 26_u8 = Ctrl+z - to end sending data + .map_err(|err| ModemError::SendDataError(format!("{:?}", err)))?; + self.command_read_response() } pub fn gprs_status(&mut self) -> Result { @@ -280,7 +298,8 @@ impl Modem { let _ = self.send_command(Command::http_set_content("application/json")); let _ = self.send_command(Command::http_set_ssl(true)); let _ = self.send_command(Command::http_post_len(content.len(), 100000)); - let _ = self.serial.send_bytes(content, Some(26)); + let _ = self.serial.write_bytes(content); + let _ = self.serial.write(&[26_u8]); let _ = self.send_command(Command::http_post()); self.send_command(Command::http_read_response()) } @@ -322,20 +341,15 @@ impl Modem { fn file_write(&mut self, buf: &[u8], path: &str, append: bool, input_time_sec: usize) -> Result<()> { let cmd = Command::fs_file_write(path, append, buf.len(), input_time_sec); let _ = self.serial - .send_bytes(cmd.text.as_bytes(), Some('\r' as u8)) + .write(cmd.text.as_bytes()) .map_err(|err| ModemError::SendDataError(format!("File write error ({:?})", err)))?; - let send_prompt: String = self.serial.rx.reset(Duration::from_millis(3000)) - .map(char::from) - .take_while(|c| *c != '>').collect(); - if send_prompt == "" { - return Err(ModemError::SendDataError("Prompt empty, expected: \\r\\n".to_string())); - } + let _ = self.handle_prompt()?; self.serial - .send_bytes(buf, None) + .write(buf) .map_err(|err| ModemError::SendDataError(format!("Error sending bytes via serial ({:?})", err)))?; - let _ = self.read_response(Some("OK".to_string()), Duration::from_millis(3000)); + let _ = self.command_read_response(); Ok(()) } diff --git a/src/serial.rs b/src/serial.rs index 79c6b12..d18efee 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -1,14 +1,14 @@ use std::error::Error; use std::io; use std::thread; -use std::time::{Duration, Instant}; +use std::time::Duration; use embedded_hal::serial::{Read, Write}; use esp_idf_hal::serial::{self, Rx, Tx}; #[derive(Debug)] pub enum SerialError { - ReadError, - WriteError, + ReadError(String), + WriteError(String), TimeoutError, } @@ -20,121 +20,81 @@ impl std::fmt::Display for SerialError { } } -pub type Result = std::result::Result; - -pub struct RxIter { - inner: Rx, - timeout: Duration, -} - -impl RxIter { - pub fn reset(&mut self, timeout: Duration) -> &mut Self { - self.timeout = timeout; - self - } - - fn clear(&mut self) -> () { - println!("clearing serial rx"); - self.reset(Duration::from_millis(500)).for_each(drop); - } -} - -impl Iterator for RxIter { - type Item = u8; - - /// `nb` returns Ok(byte), or one of Err(WouldBlock) and Err(Other) which isn't of anyone's - /// interest, so the retry mechanism is triggered on _any_ error every 200ms until a byte is - /// received, or the timeout is reached. - fn next(&mut self) -> Option { - let start = Instant::now(); - loop { - match self.inner.read() { - Ok(b) => { - self.timeout = self.timeout.saturating_sub(start.elapsed()); - break Some(b) - }, - Err(_) => { - if start.elapsed() > self.timeout { - self.timeout = Duration::ZERO; - break None - } - thread::sleep(Duration::from_millis(200)); - } - } - } - } -} +pub type Result = nb::Result; pub struct SerialIO { - pub rx: RxIter, + pub rx: Rx, pub tx: Tx, } impl SerialIO { pub fn new(tx: Tx, rx: Rx) -> Self { - Self { - rx: RxIter { inner: rx, timeout: Duration::from_millis(0) }, - tx, - } + Self { rx, tx } } - pub fn send_bytes(&mut self, payload: &[u8], eos: Option) -> Result { + pub fn write_bytes(&mut self, payload: &[u8]) -> Result { let mut num_bytes = 0; for b in payload.iter() { - nb::block!(self.tx.write(*b)) - .map_err(|err| SerialError::WriteError)?; + self.tx.write(*b) + .map_err(|err| SerialError::WriteError( + format!("Error writing in serial port ({:?})", err)))?; num_bytes += 1; } if num_bytes == payload.len() { - eos.map(|b| nb::block!(self.tx.write(b))); - Ok(num_bytes + if eos.is_none() { 0 } else { 1 }) + Ok(num_bytes) } else { - Err(SerialError::WriteError) + Err(nb::Error::Other( + SerialError::WriteError( + "Written bytes shorter than payload length (write_bytes)".to_string() + ) + )) } } - /// Reads a whole line (that ends with \\n) within the given `timeout` passed on input. - pub fn read_line(&mut self, timeout: Duration) -> Result { - let mut line: String = self.rx.reset(timeout) - .map(|b| char::from(b)) - .take_while(|c| *c != '\n') - .collect(); + fn read_bytes(&mut self, buf: &mut [u8]) -> Result { + let mut started_reading = false; + let mut count = 0; + let mut retries = 0; - // TODO: \r\n is true for sim800l, but might not be valud for other devices. Re-implement - // this function so that it can be used on all devices. - // \r must come right before \n on read; take_while excludes the matched element. - if line.ends_with('\r') { - line.push('\n'); - Ok(line) - } - else if self.rx.timeout.as_millis() == 0 { - Err(SerialError::TimeoutError) - } - else { - Err(SerialError::ReadError) - } + loop { + match self.rx.read() { + Ok(b) => { + started_reading = true; + if count < buf.len() { + buf[count] = b; + count += 1; + } + else { break } + }, + Err(nb::Error::WouldBlock) => { + if started_reading || retries > READ_MAX_RETRIES { break } + else { + thread::sleep(Duration::from_millis(READ_WAIT_TIME)); + retries += 1; + } + }, + Err(nb::Error::Other(err)) => println!("Serial read error :: {:?}", err), + } + }; + Ok(count) } } +const READ_MAX_RETRIES: usize = 5; +const READ_WAIT_TIME: u64 = 50; + impl io::Read for SerialIO { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let buf_size = buf.len(); - let count = self.rx.reset(Duration::from_millis(1000)) - .enumerate() - .map(|(i, b)| { - buf[i] = b; - i - }) - .take_while(|i| i < &buf_size) - .count(); + let count = nb::block!(self.read_bytes(buf)) + .map_err(|_| io::Error::from(io::ErrorKind::Other))?; Ok(count) } } impl io::Write for SerialIO { fn write(&mut self, buf: &[u8]) -> io::Result { - self.send_bytes(buf, None) + nb::block!(self.write_bytes(buf)) .map_err(|_| io::Error::from(io::ErrorKind::Other)) } From 98d0a66cf3aac669e393808eec48b14e318da6b9 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Tue, 29 Nov 2022 21:34:38 +0100 Subject: [PATCH 19/36] debug and fix a bunch of modem read/write errors --- src/accel.rs | 11 ++-- src/command.rs | 2 +- src/main.rs | 18 +++--- src/modem.rs | 162 ++++++++++++++++++++++++++++++------------------- src/serial.rs | 12 ++-- 5 files changed, 121 insertions(+), 84 deletions(-) diff --git a/src/accel.rs b/src/accel.rs index 50c0e56..1936c7a 100644 --- a/src/accel.rs +++ b/src/accel.rs @@ -6,12 +6,13 @@ use std::time::Duration; use crate::types::*; -pub fn main(sender: SyncSender) -> Result<(), anyhow::Error> { +pub fn main(sender: SyncSender) -> anyhow::Result<()> { + let mut c = 1_usize; println!("entering ACCELERATOR sender loop ..."); - for i in 0..20 { - println!("sending ACCELERATOR message ({}) of 20 ...", i); + loop { + println!("sending ACCELERATOR message No. {}", c); let _ = sender.send(Msg::Accelerometer("{\"velocity\": 21.43, \"altitude\": 367}".to_string()))?; - thread::sleep(Duration::from_millis(2000)); + thread::sleep(Duration::from_secs(5)); + c += 1; } - Ok(()) } diff --git a/src/command.rs b/src/command.rs index ce95655..00fb9ed 100644 --- a/src/command.rs +++ b/src/command.rs @@ -268,7 +268,7 @@ impl Command { Command { text: format!("AT+CIPSTART=\"TCP\",\"{}\",\"{}\"", addr, port), timeout: Duration::from_millis(5000), - contains: Some("CONNECT OK".to_string()), + contains: Some("OK".to_string()), } } diff --git a/src/main.rs b/src/main.rs index 72c2d8f..cd55843 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,7 @@ mod serial; mod types; use anyhow; -use std::{ - thread::{self, JoinHandle}, - time::Duration, -}; - +use std::{thread::{self, JoinHandle}, time::Duration}; use esp_idf_hal::peripherals::Peripherals; use types::*; @@ -35,11 +31,11 @@ fn main() -> anyhow::Result<()> { let mut threads: Vec>> = vec![]; - // Rx/Tx pins for the GPS modem - let gps_rx = dp.pins.gpio13; - let gps_tx = dp.pins.gpio12; - // UART interface for the GPS modem - let gps_uart = dp.uart2; + // // Rx/Tx pins for the GPS modem + // let gps_rx = dp.pins.gpio13; + // let gps_tx = dp.pins.gpio12; + // // UART interface for the GPS modem + // let gps_uart = dp.uart2; let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); @@ -49,7 +45,7 @@ fn main() -> anyhow::Result<()> { // threads.push(thread::spawn(move || gps::main(gps_rx, gps_tx, gps_uart, gps_sender))); //thread::sleep(Duration::from_millis(1000)); threads.push(thread::spawn(move || accel::main(accel_sender))); - //thread::sleep(Duration::from_millis(1000)); + thread::sleep(Duration::from_millis(3000)); let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; diff --git a/src/modem.rs b/src/modem.rs index 11cca89..1ad179a 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -16,10 +16,8 @@ use esp_idf_hal::serial::{self, Rx, Tx}; use embedded_hal::digital::v2::OutputPin; -use mqtt::packet::{ConnectPacket, PublishPacket, QoSWithPacketIdentifier}; -use mqtt::{Encodable, TopicName}; - -const MAX_TCP_MANUAL_REPLY_SIZE: usize = 300; +use mqtt::packet::{ConnectPacket, PublishPacket, QoSWithPacketIdentifier, VariablePacket}; +use mqtt::{Encodable, Decodable, TopicName}; pub type Result = std::result::Result; @@ -32,7 +30,7 @@ pub enum ModemError { CommandError(String), SetupError(String), SendDataError(String), - ReadError, + ReadError(String), TimeoutError, } @@ -77,10 +75,14 @@ impl Modem { thread::sleep(Duration::from_millis(1000)); pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; println!("Waiting for sim module to come online ..."); + thread::sleep(Duration::from_millis(3000)); loop { match self.send_command(Command::probe()) { Ok(_) => break, - _ => continue, + _ => { + thread::sleep(Duration::from_millis(2000)); + continue + }, } } Ok(()) @@ -90,16 +92,17 @@ impl Modem { /// None, then the first line is returned. If a timeout is reached. The timeout is provided on /// input via the `timeout` argument. The first argument `contains` is checked against every /// line in the response. - fn command_read_response(&mut self) -> Result { + fn command_read_response(&mut self, contains: Option) -> Result { let mut response = String::new(); loop { let mut buf = vec![0; 1024]; let num_bytes = self.serial .read(buf.as_mut_slice()) - .map_err(|_| ModemError::ReadError)?; + .map_err(|err| ModemError::ReadError(format!("Error in serial.read(buf) ({:?})", err)))?; - response.push_str(std::str::from_utf8(&buf[0..num_bytes]).map_err(|_| ModemError::ReadError)?); + response.push_str(std::str::from_utf8(&buf[0..num_bytes]) + .map_err(|err| ModemError::ReadError(format!("Error in str::from_utf8 ({:?})", err)))?); if num_bytes < buf.len() { break @@ -107,12 +110,20 @@ impl Modem { } print!("Read {} bytes from serial: {}", response.len(), response); - Ok(response) + if let Some(c) = contains { + if response.contains(&c) { + Ok(response) + } else { + Err(ModemError::CommandError(format!("Didn't get expected ({}) from modem.", c))) + } + } else { + Ok(response) + } } fn send_command(&mut self, cmd: Command) -> Result { println!("-----------------------------------------------------------"); - println!("Sending to TX ({}) ...", cmd.text); + println!("Sending {} ...", cmd.text); let _ = self.serial .write_bytes(cmd.text.as_bytes()) @@ -122,16 +133,23 @@ impl Modem { .write(&['\r' as u8]) .map_err(|_| ModemError::SendDataError(format!("Error in send_command({})", cmd.text)))?; - self.command_read_response() + self.command_read_response(cmd.contains) } fn handle_prompt(&mut self) -> Result<()> { - let mut prompt_buf = vec![0; 3]; - let prompt_len = self.serial.read(&mut prompt_buf).map_err(|_| ModemError::ReadError)?; - let prompt = std::str::from_utf8(prompt_buf.as_slice()).unwrap_or(""); + let mut prompt_buf = vec![0; 256]; + let prompt_len = self.serial.read(&mut prompt_buf) + .map_err(|err| ModemError::ReadError(format!("Error in handle_prompt() ({:?})", err)))?; - if prompt_len != 3 && prompt != "\r\n>" { - let msg = format!("Prompt error, expected \\r\\n>, got {:?}", prompt); + let prompt = String::from_utf8(prompt_buf[0..prompt_len].to_vec()) + .unwrap_or("".to_string()) + .trim() + .to_string(); + + println!("Prompt is: ({})", prompt); + + if prompt != ">" { + let msg = format!("Prompt error, expected (>), got ({})", prompt); Err(ModemError::SendDataError(msg)) } else { Ok(()) @@ -139,19 +157,34 @@ impl Modem { } fn tcp_manual_send_data(&mut self, buf: &[u8]) -> Result { + println!("Sending AT+CIPSEND to serial TX!"); let _ = self.serial .write("AT+CIPSEND\r".as_bytes()) .map_err(|_| ModemError::SendDataError("Error in tcp_manual_send_data ... AT_CIPSEND\\r".to_string()))?; let _ = self.handle_prompt()?; + println!("Handled prompt OK!!"); + println!("Writing bytes in serial TX! ({:?})", String::from_utf8(buf.to_vec())); self.serial .write_bytes(buf) .map_err(|err| ModemError::SendDataError(format!("{:?}", err)))?; self.serial .write(&[26_u8]) // 26_u8 = Ctrl+z - to end sending data .map_err(|err| ModemError::SendDataError(format!("{:?}", err)))?; - self.command_read_response() + println!("DONE Writing bytes in serial TX!"); + + thread::sleep(Duration::from_millis(500)); + + println!("Reading bytes in serial RX!"); + for _ in 0..3 { + let res = self.command_read_response(Some("SEND OK".into())); + if res.is_ok() { + return res; + } + thread::sleep(Duration::from_millis(1000)) + } + Err(ModemError::ReadError(format!("ReadError: cannot read serial RX!"))) } pub fn gprs_status(&mut self) -> Result { @@ -218,8 +251,15 @@ impl Modem { } pub fn tcp_connect(&mut self, addr: &str, port: u16) -> Result<()> { - self.send_command(Command::tcp_connect(addr, port)) - .map(|_| ()) + let _ = self.send_command(Command::tcp_connect(addr, port))?; + for _ in 0..3 { + if let Ok(reply) = self.command_read_response(Some("CONNECT_OK".to_string())) { + println!("TCP connect replied with {}", reply); + break + } + thread::sleep(Duration::from_millis(500)); + } + Ok(()) } pub fn tcp_set_quick_mode(&mut self, mode: bool) -> Result<()> { @@ -248,18 +288,21 @@ impl Modem { pub fn tcp_receive_reply_len(&mut self) -> Result { let reply = self.send_command(Command::tcp_receive_reply_len())?; - reply.lines() + println!("Receiving TCP reply length!"); + let res = reply.lines() .filter(|line| line.contains("+CIPRXGET: 4")) .next() - .ok_or(ModemError::CommandError("reply not found :/".to_string())) - .map(|line| self.tcp_parse_response_size(line)) - .unwrap_or(Err(ModemError::CommandError(format!("received 0 elements from parsing")))) + .ok_or(ModemError::CommandError("reply body missing :/".to_string())) + .and_then(|line| self.tcp_parse_response_size(line)) + .map_err(|_| ModemError::CommandError(format!("received 0 elements from parsing"))); + println!("Received ({:?})", res); + res } pub fn tcp_receive(&mut self, buf: &mut [u8]) -> Result { let mut size = 0; loop { - let reply = self.send_command(Command::tcp_receive(MAX_TCP_MANUAL_REPLY_SIZE)) + let reply = self.send_command(Command::tcp_receive(buf.len())) .map(|reply| { // TODO: parse the response properly // 1. the first line is \r\n @@ -349,7 +392,7 @@ impl Modem { self.serial .write(buf) .map_err(|err| ModemError::SendDataError(format!("Error sending bytes via serial ({:?})", err)))?; - let _ = self.command_read_response(); + let _ = self.command_read_response(None); Ok(()) } @@ -380,33 +423,44 @@ impl Modem { .map(|_| ()) } - fn mqtt_receive_reply(&mut self) -> std::result::Result<(), anyhow::Error> { - println!("receiving mqtt reply from modem ..."); - let size = self.tcp_receive_reply_len()?; - println!("receiving reply len({}) ...", size); - let mut reply = vec![0 as u8; size]; - println!("receiving tcp reply ..."); - let _ = self.tcp_receive(&mut reply); - println!("received tcp reply ..."); - Ok(()) + fn mqtt_receive_reply(&mut self) -> Result { + for _ in 0..3 { + let size = self.tcp_receive_reply_len()?; + println!("received reply len({}) ...", size); + if size == 0 { + println!("retrying ..."); + continue + } else { + let mut reply = vec![0 as u8; size]; + println!("receiving mqtt reply ..."); + let _ = self.tcp_receive(&mut reply); + let reply = std::str::from_utf8(&reply).unwrap_or(""); + println!("received mqtt reply ({})", reply); + return VariablePacket::decode(&mut reply.as_bytes()) + .map_err(|err| ModemError::CommandError(format!("Undecodable MQTT message. ({:?})", err))); + } + } + Err(ModemError::ReadError("TCP server didn't respond!".into())) } - fn mqtt_connect(&mut self, device_id: &str) -> std::result::Result<(), anyhow::Error> { + fn mqtt_connect(&mut self, device_id: &str) -> anyhow::Result<()> { let mut buf = Vec::new(); let mut conn = ConnectPacket::new(device_id); conn.set_clean_session(true); conn.set_keep_alive(100); let _ = conn.encode(&mut buf)?; - let _ = self.tcp_manual_send(&mut buf)?; + self.tcp_manual_send(&mut buf).ok(); - thread::sleep(Duration::from_millis(2000)); - drop(buf); + let reply = self.mqtt_receive_reply()?; + println!("mqtt decoded packet: ({:?})", reply); - let _ = self.mqtt_receive_reply()?; - Ok(()) + match reply { + VariablePacket::ConnackPacket(_) => Ok(()), + _ => Err(anyhow::Error::msg("Invalid MQTT reply ... expected CONNACK!")) + } } - fn mqtt_publish(&mut self, _device_id: &str, message: &str) -> std::result::Result<(), anyhow::Error> { + fn mqtt_publish(&mut self, _device_id: &str, message: &str) -> anyhow::Result<()> { println!("entered mqtt publish ..."); let mut buf = Vec::new(); let packet = PublishPacket::new( @@ -414,16 +468,10 @@ impl Modem { QoSWithPacketIdentifier::Level0, message.as_bytes(), ); - println!("created mqtt publish packet ..."); let _ = packet.encode(&mut buf)?; - println!("modem tcp send publish pakage ..."); - let _ = self.tcp_manual_send(&mut buf)?; - - thread::sleep(Duration::from_millis(2000)); - drop(buf); - - println!("receiving modem publish reply ..."); - let _ = self.mqtt_receive_reply()?; + println!("created mqtt publish packet ... ({})", + std::str::from_utf8(buf.as_slice()).unwrap_or("")); + self.tcp_manual_send(&mut buf).ok(); Ok(()) } } @@ -434,18 +482,6 @@ impl std::io::Read for Modem { } } -impl std::io::Write for Modem { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.tcp_manual_send(buf) - .map_err(|_| std::io::Error::from(std::io::ErrorKind::ConnectionAborted))?; - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - pub fn main( rx: esp_idf_hal::gpio::Gpio26, tx: esp_idf_hal::gpio::Gpio27, diff --git a/src/serial.rs b/src/serial.rs index d18efee..95d05ea 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -77,7 +77,11 @@ impl SerialIO { Err(nb::Error::Other(err)) => println!("Serial read error :: {:?}", err), } }; - Ok(count) + if count > 0 { + Ok(count) + } else { + Err(nb::Error::Other(SerialError::ReadError("Rx buffer empty.".to_string()))) + } } } @@ -87,7 +91,7 @@ const READ_WAIT_TIME: u64 = 50; impl io::Read for SerialIO { fn read(&mut self, buf: &mut [u8]) -> io::Result { let count = nb::block!(self.read_bytes(buf)) - .map_err(|_| io::Error::from(io::ErrorKind::Other))?; + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; Ok(count) } } @@ -95,11 +99,11 @@ impl io::Read for SerialIO { impl io::Write for SerialIO { fn write(&mut self, buf: &[u8]) -> io::Result { nb::block!(self.write_bytes(buf)) - .map_err(|_| io::Error::from(io::ErrorKind::Other)) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } fn flush(&mut self) -> io::Result<()> { self.tx.flush() - .map_err(|_| io::Error::from(io::ErrorKind::Other)) + .map_err(|err| io::Error::new(io::ErrorKind::Other, SerialError::ReadError(format!("Flush error ({:?})", err)))) } } From 31a6228669a196ade99bd8190cb680cc91361791 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Fri, 23 Dec 2022 13:00:10 +0100 Subject: [PATCH 20/36] tydy up serial module --- src/serial.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/serial.rs b/src/serial.rs index 95d05ea..a3240c2 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -5,11 +5,13 @@ use std::time::Duration; use embedded_hal::serial::{Read, Write}; use esp_idf_hal::serial::{self, Rx, Tx}; +const READ_MAX_RETRIES: usize = 5; +const READ_WAIT_TIME: u64 = 10; + #[derive(Debug)] pub enum SerialError { ReadError(String), WriteError(String), - TimeoutError, } impl Error for SerialError {} @@ -85,9 +87,6 @@ impl SerialIO { } } -const READ_MAX_RETRIES: usize = 5; -const READ_WAIT_TIME: u64 = 50; - impl io::Read for SerialIO { fn read(&mut self, buf: &mut [u8]) -> io::Result { let count = nb::block!(self.read_bytes(buf)) From 8308bdb9a0f4c44b85ab7976d552b9ae3e10136d Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Tue, 7 Feb 2023 17:15:08 +0100 Subject: [PATCH 21/36] try to fix reading and fail at it :/ --- src/main.rs | 1 - src/modem.rs | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index cd55843..3138387 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,6 @@ fn main() -> anyhow::Result<()> { // threads.push(thread::spawn(move || gps::main(gps_rx, gps_tx, gps_uart, gps_sender))); //thread::sleep(Duration::from_millis(1000)); threads.push(thread::spawn(move || accel::main(accel_sender))); - thread::sleep(Duration::from_millis(3000)); let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; diff --git a/src/modem.rs b/src/modem.rs index 1ad179a..9381baf 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -125,12 +125,8 @@ impl Modem { println!("-----------------------------------------------------------"); println!("Sending {} ...", cmd.text); - let _ = self.serial - .write_bytes(cmd.text.as_bytes()) - .map_err(|_| ModemError::SendDataError(format!("Error in send_command({})", cmd.text)))?; - - let _ = self.serial - .write(&['\r' as u8]) + let _ = nb::block!(self.serial + .write_bytes(&[cmd.text.as_bytes(), &['\r' as u8]].concat())) .map_err(|_| ModemError::SendDataError(format!("Error in send_command({})", cmd.text)))?; self.command_read_response(cmd.contains) @@ -253,7 +249,7 @@ impl Modem { pub fn tcp_connect(&mut self, addr: &str, port: u16) -> Result<()> { let _ = self.send_command(Command::tcp_connect(addr, port))?; for _ in 0..3 { - if let Ok(reply) = self.command_read_response(Some("CONNECT_OK".to_string())) { + if let Ok(reply) = self.command_read_response(Some("CONNECT OK".to_string())) { println!("TCP connect replied with {}", reply); break } @@ -532,7 +528,11 @@ pub fn main( let _ = mdm.tcp_set_quick_mode(false); let _ = mdm.tcp_set_manual_receive(true); - let _ = mdm.tcp_connect("51.158.66.64", 7887)?; + + if let Err(_) = mdm.tcp_connect("51.158.66.64", 7887) { + continue; + } + thread::sleep(Duration::from_secs(1)); let _ = mdm.mqtt_connect(device_id)?; From b30acab82339584331fdb9ebae424499ac2c28dd Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Wed, 8 Feb 2023 01:24:54 +0100 Subject: [PATCH 22/36] clear Rx buffer before TCP connect and ofc some other things too :) --- Cargo.toml | 2 +- src/gps.rs | 2 +- src/main.rs | 3 +-- src/modem.rs | 47 ++++++++++++++++++++++++++++++++++------------- src/serial.rs | 23 ++++++++++++++++++++++- 5 files changed, 59 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e56188..4ff3999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ esp-idf-hal = "0.37.4" esp-idf-sys = { version = "0.31.5", features = ["binstart", "native"] } mqtt-protocol = "0.11.2" nb = "1.0.0" -ublox = { git = "https://github.com/lkolbly/ublox.git", branch = "master" } +ublox = "0.4.2" [build-dependencies] embuild = "0.29" diff --git a/src/gps.rs b/src/gps.rs index 025cad7..2b0b943 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -126,7 +126,7 @@ pub fn main( mode: UartMode::new(DataBits::Eight, Parity::None, StopBits::One), baud_rate: 9600, in_proto_mask: InProtoMask::all(), - out_proto_mask: OutProtoMask::UBLOX, + out_proto_mask: OutProtoMask::UBOX, flags: 0, reserved5: 0, } diff --git a/src/main.rs b/src/main.rs index 3138387..e4449fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ mod accel; mod config; #[allow(dead_code)] mod command; -mod gps; mod modem; mod serial; mod types; @@ -43,7 +42,7 @@ fn main() -> anyhow::Result<()> { // let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; // threads.push(thread::spawn(move || gps::main(gps_rx, gps_tx, gps_uart, gps_sender))); - //thread::sleep(Duration::from_millis(1000)); + thread::sleep(Duration::from_millis(1000)); threads.push(thread::spawn(move || accel::main(accel_sender))); let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; diff --git a/src/modem.rs b/src/modem.rs index 9381baf..cc313c6 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -67,10 +67,10 @@ impl Modem { pub fn init(&mut self, mut pwrkey: impl OutputPin, mut rst: impl OutputPin, mut power: impl OutputPin) -> Result<()> { println!("Turning SIM800L on ..."); power.set_high().map_err(|_| ModemError::SetupError("Error setting POWER to high.".to_string()))?; - rst.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_string()))?; + //rst.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_string()))?; // Pull down PWRKEY for more than 1 second according to manual requirements pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; - thread::sleep(Duration::from_millis(100)); + thread::sleep(Duration::from_millis(1500)); pwrkey.set_low().map_err(|_| ModemError::SetupError("Error setting PWRKEY to low.".to_string()))?; thread::sleep(Duration::from_millis(1000)); pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; @@ -122,14 +122,24 @@ impl Modem { } fn send_command(&mut self, cmd: Command) -> Result { + if let Some(contains) = cmd.contains { + self.send(&cmd.text, &contains) + } else { + self.send(&cmd.text, "") + } + } + + fn send(&mut self, at_command: &str, contains: &str) -> Result { println!("-----------------------------------------------------------"); - println!("Sending {} ...", cmd.text); + println!("Sending {} ...", at_command); let _ = nb::block!(self.serial - .write_bytes(&[cmd.text.as_bytes(), &['\r' as u8]].concat())) - .map_err(|_| ModemError::SendDataError(format!("Error in send_command({})", cmd.text)))?; + .write_bytes(&[at_command.as_bytes(), &['\r' as u8]].concat())) + .map_err(|_| ModemError::SendDataError(format!("Error in send_command({})", at_command)))?; - self.command_read_response(cmd.contains) + let contains_opt = if contains == "" { None } else { Some(contains.to_string()) }; + + self.command_read_response(contains_opt) } fn handle_prompt(&mut self) -> Result<()> { @@ -247,7 +257,8 @@ impl Modem { } pub fn tcp_connect(&mut self, addr: &str, port: u16) -> Result<()> { - let _ = self.send_command(Command::tcp_connect(addr, port))?; + let at_command = format!("AT+CIPSTART=\"TCP\",\"{}\",\"{}\"", addr, port); + let _ = self.send(&at_command, "CONNECT OK"); for _ in 0..3 { if let Ok(reply) = self.command_read_response(Some("CONNECT OK".to_string())) { println!("TCP connect replied with {}", reply); @@ -515,6 +526,9 @@ pub fn main( //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; //let _ = mdm.fs_list("C:\\USER\\")?; + // Retry 5 times to open a TCP connection, otherwise fail and wait for reboot. + let mut retries = 0; + loop { if !mdm.is_gprs_attached()? { let _ = mdm.gprs_attach_ap( @@ -526,13 +540,21 @@ pub fn main( if let Ok(()) = mdm.try_connect_gprs() { let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; - let _ = mdm.tcp_set_quick_mode(false); - let _ = mdm.tcp_set_manual_receive(true); + // When command AT+CIPQSEND=0, it is in normal sending mode. In this mode, after user + // sends data by AT+CIPSEND, if the server receives TCP data, it will give ACK message + // to module, and the module will respond SEND OK. + let _ = mdm.send("AT+CIPQSEND=0", "OK"); + // Enables getting data from network manually. + let _ = mdm.send("AT+CIPRXGET=1", "OK"); if let Err(_) = mdm.tcp_connect("51.158.66.64", 7887) { - continue; + if retries < 5 { + retries += 1; + continue; + } } - thread::sleep(Duration::from_secs(1)); + thread::sleep(Duration::from_millis(500)); + mdm.serial.clear(); let _ = mdm.mqtt_connect(device_id)?; @@ -551,7 +573,7 @@ pub fn main( err_count = 0; } Err(e) => { - if err_count < 10 { + if err_count < 5 { err_count += 1; println!("received error {} | NOT sending to mqtt ...", e); } @@ -561,7 +583,6 @@ pub fn main( } } }; - let _ = mdm.tcp_close_connection()?; } } diff --git a/src/serial.rs b/src/serial.rs index a3240c2..cccf36d 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -54,7 +54,7 @@ impl SerialIO { } } - fn read_bytes(&mut self, buf: &mut [u8]) -> Result { + pub fn read_bytes(&mut self, buf: &mut [u8]) -> Result { let mut started_reading = false; let mut count = 0; let mut retries = 0; @@ -85,6 +85,27 @@ impl SerialIO { Err(nb::Error::Other(SerialError::ReadError("Rx buffer empty.".to_string()))) } } + + pub fn clear(&mut self) { + let mut started_reading = false; + let mut retries = 0; + + loop { + match self.rx.read() { + Ok(_) => { + started_reading = true; + }, + Err(nb::Error::WouldBlock) => { + if started_reading || retries > READ_MAX_RETRIES { break; } + else { + thread::sleep(Duration::from_millis(READ_WAIT_TIME)); + retries += 1; + } + }, + Err(nb::Error::Other(err)) => println!("Serial read error :: {:?}", err), + } + } + } } impl io::Read for SerialIO { From e7c51f2b61abe3102a04806a7443d1d3aeb6c440 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Wed, 8 Feb 2023 01:33:31 +0100 Subject: [PATCH 23/36] tls function skeleton --- src/modem.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/modem.rs b/src/modem.rs index cc313c6..b09b0e7 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -269,6 +269,16 @@ impl Modem { Ok(()) } + pub fn tls_connect(&mut self, addr: &str, port: u16) -> Result<()> { + let _ = self.tcp_connect(addr, port)?; + + // ------------------------ + // TLS handshake goes here. + // ------------------------ + + Ok(()) + } + pub fn tcp_set_quick_mode(&mut self, mode: bool) -> Result<()> { self.send_command(Command::tcp_set_quick_mode(mode)) .map(|_| ()) From 576bcfc59030e1bf170821edb7ccb270d390416f Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Fri, 10 Feb 2023 11:41:39 +0100 Subject: [PATCH 24/36] make modem main args generic --- src/modem.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/modem.rs b/src/modem.rs index b09b0e7..788bf39 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -499,15 +499,23 @@ impl std::io::Read for Modem { } } -pub fn main( - rx: esp_idf_hal::gpio::Gpio26, - tx: esp_idf_hal::gpio::Gpio27, +pub fn main +( + rx: PRx, + tx: PTx, uart: serial::UART1, - pwrkey: esp_idf_hal::gpio::Gpio4, - rst: esp_idf_hal::gpio::Gpio5, - power: esp_idf_hal::gpio::Gpio23, + pwrkey: DPK, + rst: DR, + power: DP, receiver: Receiver, -) -> std::result::Result<(), anyhow::Error> { +) -> std::result::Result<(), anyhow::Error> +where + PRx: esp_idf_hal::gpio::Pin + esp_idf_hal::gpio::InputPin + esp_idf_hal::gpio::OutputPin, + PTx: esp_idf_hal::gpio::Pin + esp_idf_hal::gpio::InputPin + esp_idf_hal::gpio::OutputPin, + DPK: embedded_hal::digital::v2::OutputPin, + DR: embedded_hal::digital::v2::OutputPin, + DP: embedded_hal::digital::v2::OutputPin, +{ let serial_pins = serial::Pins { tx, rx, From ff779d0dc34c501911834818f151e22e20ec8a53 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sun, 12 Feb 2023 11:59:33 +0100 Subject: [PATCH 25/36] clear RX before sending AT commands --- src/modem.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modem.rs b/src/modem.rs index 788bf39..6018de2 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -290,6 +290,8 @@ impl Modem { } pub fn tcp_manual_send(&mut self, buf: &[u8]) -> Result<()> { + thread::sleep(Duration::from_millis(200)); + self.serial.clear(); self.tcp_manual_send_data(buf) .map(|_| ()) } @@ -556,8 +558,6 @@ where )?; } if let Ok(()) = mdm.try_connect_gprs() { - let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; - // When command AT+CIPQSEND=0, it is in normal sending mode. In this mode, after user // sends data by AT+CIPSEND, if the server receives TCP data, it will give ACK message // to module, and the module will respond SEND OK. @@ -571,9 +571,8 @@ where continue; } } - thread::sleep(Duration::from_millis(500)); - mdm.serial.clear(); + let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; let _ = mdm.mqtt_connect(device_id)?; println!("entering queue receive loop ..."); From 47b333d354b91f18c7012e5b7e96b1e91dd8890e Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sun, 12 Feb 2023 17:55:54 +0100 Subject: [PATCH 26/36] use whole AP config as input arg --- src/config.rs | 8 +++++++- src/modem.rs | 17 +++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/config.rs b/src/config.rs index 62f368f..85dc37a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,8 +4,14 @@ pub struct GprsAp<'a> { pub password: &'a str, } -pub const A1_GPRS_AP: GprsAp = GprsAp { +pub const A1: GprsAp = GprsAp { apn: "internet", username: "internet", password: "internet", }; + +pub const MTS: GprsAp = GprsAp { + apn: "gprswap", + username: "mts", + password: "064", +}; diff --git a/src/modem.rs b/src/modem.rs index 6018de2..78d0cb9 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -197,15 +197,15 @@ impl Modem { self.send_command(Command::gprs_bearer_status()) } - pub fn gprs_attach_ap(&mut self, apn: &str, username: &str, password: &str)-> Result<()> { + pub fn gprs_attach_ap(&mut self, config: crate::config::GprsAp)-> Result<()> { println!("init gprs ..."); let _ = self.send_command(Command::gprs_init())?; - println!("setting up gprs credentials for apn {}, {}:{})", apn, username, password); + println!("setting up gprs credentials for apn {}, {}:{})", config.apn, config.username, config.password); - let _ = self.send_command(Command::gprs_set_apn(apn))?; - let _ = self.send_command(Command::gprs_set_user(username))?; - let _ = self.send_command(Command::gprs_set_pwd(password))?; + let _ = self.send_command(Command::gprs_set_apn(config.apn))?; + let _ = self.send_command(Command::gprs_set_user(config.username))?; + let _ = self.send_command(Command::gprs_set_pwd(config.password))?; Ok(()) } @@ -223,6 +223,7 @@ impl Modem { fn try_connect_gprs(&mut self) -> Result<()> { let mut retries = 0; + println!("TRYING TO CONNECT TO GPRS"); loop { if self.is_gprs_attached()? { let _ = self.gprs_connect()?; @@ -551,11 +552,7 @@ where loop { if !mdm.is_gprs_attached()? { - let _ = mdm.gprs_attach_ap( - crate::config::A1_GPRS_AP.apn, - crate::config::A1_GPRS_AP.username, - crate::config::A1_GPRS_AP.password, - )?; + let _ = mdm.gprs_attach_ap(crate::config::A1)?; } if let Ok(()) = mdm.try_connect_gprs() { // When command AT+CIPQSEND=0, it is in normal sending mode. In this mode, after user From 10c1018e07d052ef4181171c0a61333823d269bb Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 13 Feb 2023 00:38:09 +0100 Subject: [PATCH 27/36] read MQTT username and password from files and some other tweaks here and there ... --- .gitignore | 1 + secret/.keep | 0 src/command.rs | 4 ++-- src/config.rs | 2 ++ src/modem.rs | 35 ++++++++++++++++++++++------------- src/types.rs | 3 ++- 6 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 secret/.keep diff --git a/.gitignore b/.gitignore index 73a638b..bfa5184 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.embuild /target /Cargo.lock +/secret diff --git a/secret/.keep b/secret/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/command.rs b/src/command.rs index 00fb9ed..13c5370 100644 --- a/src/command.rs +++ b/src/command.rs @@ -12,7 +12,7 @@ impl Command { Command { text: "ATI".to_string(), timeout: Duration::from_millis(6000), - contains: Some("+CIEV".to_string()), + contains: Some("OK".to_string()), } } @@ -236,7 +236,7 @@ impl Command { Command { text: "AT".to_string(), timeout: Duration::from_millis(3000), - contains: Some("+CIEV".to_string()), + contains: Some("OK".to_string()), } } diff --git a/src/config.rs b/src/config.rs index 85dc37a..67e62e5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + pub struct GprsAp<'a> { pub apn: &'a str, pub username: &'a str, diff --git a/src/modem.rs b/src/modem.rs index 78d0cb9..4594936 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use crate::command::Command; use crate::serial::SerialIO; use crate::types::*; @@ -67,7 +69,7 @@ impl Modem { pub fn init(&mut self, mut pwrkey: impl OutputPin, mut rst: impl OutputPin, mut power: impl OutputPin) -> Result<()> { println!("Turning SIM800L on ..."); power.set_high().map_err(|_| ModemError::SetupError("Error setting POWER to high.".to_string()))?; - //rst.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_string()))?; + rst.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_string()))?; // Pull down PWRKEY for more than 1 second according to manual requirements pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; thread::sleep(Duration::from_millis(1500)); @@ -76,18 +78,18 @@ impl Modem { pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; println!("Waiting for sim module to come online ..."); thread::sleep(Duration::from_millis(3000)); - loop { - match self.send_command(Command::probe()) { - Ok(_) => break, - _ => { - thread::sleep(Duration::from_millis(2000)); - continue - }, - } + for _ in 0..10 { + let _ = self.send_command(Command::probe()).unwrap_or("".to_string()); + thread::sleep(Duration::from_millis(1000)); } Ok(()) } + pub fn echo(&mut self, enabled: bool) -> Result<()> { + let cmd = format!("ATE{}", if enabled { 1 } else { 0 }); + self.send(&cmd, "OK").map(|_| ()) + } + /// Reads the serial RX until the `contains` string is encoutered if `contains` is Some(s), if /// None, then the first line is returned. If a timeout is reached. The timeout is provided on /// input via the `timeout` argument. The first argument `contains` is checked against every @@ -114,7 +116,7 @@ impl Modem { if response.contains(&c) { Ok(response) } else { - Err(ModemError::CommandError(format!("Didn't get expected ({}) from modem.", c))) + Err(ModemError::CommandError(format!("Didn't get expected ({}) from modem. Got: {}", c, response))) } } else { Ok(response) @@ -463,11 +465,13 @@ impl Modem { Err(ModemError::ReadError("TCP server didn't respond!".into())) } - fn mqtt_connect(&mut self, device_id: &str) -> anyhow::Result<()> { + fn mqtt_connect(&mut self, device_id: &str, username: &str, password: &str) -> anyhow::Result<()> { let mut buf = Vec::new(); let mut conn = ConnectPacket::new(device_id); conn.set_clean_session(true); conn.set_keep_alive(100); + conn.set_user_name(Some(username.to_string())); + conn.set_password(Some(password.to_string())); let _ = conn.encode(&mut buf)?; self.tcp_manual_send(&mut buf).ok(); @@ -535,6 +539,9 @@ where let (tx, rx) = serial.split(); let mut mdm = Modem::new(tx, rx); + let mqtt_username = include_str!("../secret/username").trim(); + let mqtt_password = include_str!("../secret/password").trim(); + mdm.init(pwrkey, rst, power)?; // thread::sleep(Duration::from_millis(500)); @@ -547,12 +554,13 @@ where //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; //let _ = mdm.fs_list("C:\\USER\\")?; + let _ = mdm.echo(false); // Retry 5 times to open a TCP connection, otherwise fail and wait for reboot. let mut retries = 0; loop { if !mdm.is_gprs_attached()? { - let _ = mdm.gprs_attach_ap(crate::config::A1)?; + let _ = mdm.gprs_attach_ap(crate::config::MTS)?; } if let Ok(()) = mdm.try_connect_gprs() { // When command AT+CIPQSEND=0, it is in normal sending mode. In this mode, after user @@ -570,7 +578,8 @@ where } let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; - let _ = mdm.mqtt_connect(device_id)?; + println!("connecting to MQTT with ({}:{})", mqtt_username, mqtt_password); + let _ = mdm.mqtt_connect(device_id, mqtt_username, mqtt_password)?; println!("entering queue receive loop ..."); let mut err_count = 0; diff --git a/src/types.rs b/src/types.rs index daed150..ff52839 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + #[derive(Debug)] pub struct Solution { pub latitude: f64, @@ -11,4 +13,3 @@ pub enum Msg { Gps(Solution), Accelerometer(String), } - From 09402bbf83e6e5a45a1b84ce19a342f8d55e1ddb Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sat, 18 Feb 2023 15:42:11 +0100 Subject: [PATCH 28/36] gps and mqtt pub/sub works! --- Cargo.toml | 4 +- sdkconfig.defaults | 2 +- src/gps.rs | 195 ++++++++++----------------------------------- src/main.rs | 15 ++-- src/modem.rs | 20 ++++- src/types.rs | 103 ++++++++++++++++++++++-- 6 files changed, 168 insertions(+), 171 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ff3999..5eaa44d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,9 @@ esp-idf-hal = "0.37.4" esp-idf-sys = { version = "0.31.5", features = ["binstart", "native"] } mqtt-protocol = "0.11.2" nb = "1.0.0" -ublox = "0.4.2" +nmea0183 = "0.3.0" +serde-json-core = "0.5.0" +serde = "*" [build-dependencies] embuild = "0.29" diff --git a/sdkconfig.defaults b/sdkconfig.defaults index cd6dab8..6e54cee 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,5 +1,5 @@ # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) -CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=128000 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). diff --git a/src/gps.rs b/src/gps.rs index 2b0b943..40e84af 100644 --- a/src/gps.rs +++ b/src/gps.rs @@ -3,96 +3,41 @@ use anyhow; use std::{ sync::mpsc::SyncSender, thread, - time::{Duration, Instant}, - io::{Read, Write}, + time::Duration, + io::Read, }; use esp_idf_hal::prelude::*; use esp_idf_hal::serial::{self, Rx, Tx}; -use ublox::*; +use nmea0183::{Parser, ParseResult, Sentence, Source}; -use crate::types::*; +use crate::types::Msg; use crate::serial::SerialIO; struct GpsModule { port: SerialIO, - parser: Parser>, } impl GpsModule { pub fn new(tx: Tx, rx: Rx) -> Self { - let parser = Parser::default(); - GpsModule { port: SerialIO::new(tx, rx), parser } - } - - pub fn write_all(&mut self, data: &[u8]) -> std::io::Result<()> { - println!("WRITE: {:?}", data); - self.port.write(data).map(|_| ()) - } - - pub fn update(&mut self, mut cb: T) -> std::io::Result<()> { - println!("UPDATING ... ... ..."); - loop { - let mut local_buf = [0; 1024]; - let nbytes = self.read_port(&mut local_buf)?; - if nbytes == 0 { - println!("no bytes to read :("); - break; - } - // parser.consume adds the buffer to its internal buffer, and - // returns an iterator-like object we can use to process the packets - let mut it = self.parser.consume(&local_buf[..nbytes]); - while let Some(Ok(packet)) = it.next() { - println!("READ: {:?}", packet); - cb(packet); - } - } - Ok(()) - } - - pub fn wait_for_ack(&mut self, timeout: Duration) -> std::io::Result { - let mut found_packet = false; - println!("LOOKING FOR ACK ..."); - let start = Instant::now(); - while !found_packet && start.elapsed() < timeout { - self.update(|packet| { - if let PacketRef::AckAck(ack) = packet { - if ack.class() == T::CLASS && ack.msg_id() == T::ID { - println!("FOUND PACKET: {} {}", ack.class(), ack.msg_id()); - found_packet = true; - } - } - else if let PacketRef::AckNak(nak) = packet { - println!("NAK PACKET: {} {}", nak.class(), nak.msg_id()); - } - })?; - } - println!("exiting wait_for_ack"); - Ok(found_packet) - } - - /// Reads the serial port, converting timeouts into "no data received" - fn read_port(&mut self, output: &mut [u8]) -> std::io::Result { - match self.port.read(output) { - Ok(b) => Ok(b), - Err(e) => { - if e.kind() == std::io::ErrorKind::TimedOut { - Ok(0) - } else { - Err(e) - } - } + GpsModule { + port: SerialIO::new(tx, rx), } } } -pub fn main( - tx: esp_idf_hal::gpio::Gpio12, - rx: esp_idf_hal::gpio::Gpio13, +pub fn main +( + tx: PTx, + rx: PRx, uart: serial::UART2, sender: SyncSender, -) -> Result<(), anyhow::Error> { +) -> std::result::Result<(), anyhow::Error> +where + PRx: esp_idf_hal::gpio::Pin + esp_idf_hal::gpio::InputPin + esp_idf_hal::gpio::OutputPin, + PTx: esp_idf_hal::gpio::Pin + esp_idf_hal::gpio::InputPin + esp_idf_hal::gpio::OutputPin, +{ let serial_pins = serial::Pins { tx, rx, @@ -100,100 +45,46 @@ pub fn main( rts: None, }; - let serial_config = serial::config::Config::default() - .baudrate(Hertz(9600)) - .data_bits(serial::config::DataBits::DataBits8) - .parity_none() - .flow_control(serial::config::FlowControl::None) - .stop_bits(serial::config::StopBits::STOP1); - let serial: serial::Serial = serial::Serial::new( uart, serial_pins, - serial_config, + serial::config::Config::default().baudrate(Hertz(9600)), )?; let (tx, rx) = serial.split(); let mut device = GpsModule::new(tx, rx); - // Configure the device to talk UBX - device - .write_all( - &CfgPrtUartBuilder { - portid: UartPortId::Uart1, - reserved0: 0, - tx_ready: 0, - mode: UartMode::new(DataBits::Eight, Parity::None, StopBits::One), - baud_rate: 9600, - in_proto_mask: InProtoMask::all(), - out_proto_mask: OutProtoMask::UBOX, - flags: 0, - reserved5: 0, - } - .into_packet_bytes(), - )?; - device.wait_for_ack::(Duration::from_millis(3000)).unwrap(); - println!("CfgPrtUart acked!"); + let mut parser = Parser::new() + .source_only(Source::GPS) + .sentence_filter(Sentence::GLL | Sentence::GGA); - // Set interval for the NavPosVelTime packet - println!("Sending set_rate_for:: ..."); - for i in 1..5 { - device - .write_all( - &CfgMsgAllPortsBuilder::set_rate_for::([0, 1, 1, 0, 0, 0]) - .into_packet_bytes(), - ) - .unwrap(); - println!("SENT set_rate_for::({}) !!!", i); - if let Ok(true) = device.wait_for_ack::(Duration::from_millis(3000)) { - println!("Setting rate for NavPosVelTime acked! Exiting loop ..."); - break - } - } - - // Send a packet request for the MonVer packet - //device - // .write_all(&UbxPacketRequest::request_for::().into_packet_bytes()) - // .unwrap(); - - // Start reading data - println!("Opened u-blox device, waiting for solutions..."); - for _ in 0..20 { - device - .update(|packet| match packet { - PacketRef::MonVer(packet) => { - println!( - "SW version: {} HW version: {}", - packet.software_version(), - packet.hardware_version() - ); - println!("{:?}", packet); - } - PacketRef::NavPosVelTime(sol) => { - let has_posvel = sol.fix_type() == GpsFix::Fix3D - || sol.fix_type() == GpsFix::GPSPlusDeadReckoning; - - if has_posvel { - let pos: Position = (&sol).into(); - let vel: Velocity = (&sol).into(); - let solution = Solution { - latitude: pos.lat, - longitude: pos.lon, - altitude: pos.alt, - speed: vel.speed, - direction: vel.heading, - }; - println!("Sol: {:?}", solution); - sender.send(Msg::Gps(solution)); + let mut c = 0; + let mut nmea = [0_u8; 1024]; + loop { + if let Ok(_) = device.port.read(nmea.as_mut_slice()) { + println!("\r\n\r\n\r\n\r\n"); + for result in parser.parse_from_bytes(&nmea[..]) { + match result { + Ok(ParseResult::GLL(Some(gll))) => { + sender.send(Msg::Gps(gll.into()))?; + }, + Ok(ParseResult::GGA(Some(gga))) => { + sender.send(Msg::Gps(gga.into()))?; } + _ => { } } - _ => { - println!("{:?}", packet); - } - })?; - thread::sleep(Duration::from_millis(1000)); + } + c = 0; + } else { + println!("nothing to read after {} tries ...", c); + if c > 100 { + println!("reached {} retries ... bailing!", c); + break; + } + } + thread::sleep(Duration::from_millis(5000)); + c += 1; } - println!("exiting GPS sender loop :)"); Ok(()) } diff --git a/src/main.rs b/src/main.rs index e4449fd..948a843 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod command; mod modem; mod serial; mod types; +mod gps; use anyhow; use std::{thread::{self, JoinHandle}, time::Duration}; @@ -31,19 +32,19 @@ fn main() -> anyhow::Result<()> { let mut threads: Vec>> = vec![]; // // Rx/Tx pins for the GPS modem - // let gps_rx = dp.pins.gpio13; - // let gps_tx = dp.pins.gpio12; + let gps_rx = dp.pins.gpio32; + let gps_tx = dp.pins.gpio33; // // UART interface for the GPS modem - // let gps_uart = dp.uart2; + let gps_uart = dp.uart2; let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); - let accel_sender = gps_sender.clone(); + //let accel_sender = gps_sender.clone(); - // let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; - // threads.push(thread::spawn(move || gps::main(gps_rx, gps_tx, gps_uart, gps_sender))); + //let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; + threads.push(thread::spawn(move || gps::main(gps_tx, gps_rx, gps_uart, gps_sender))); thread::sleep(Duration::from_millis(1000)); - threads.push(thread::spawn(move || accel::main(accel_sender))); + //threads.push(thread::spawn(move || accel::main(accel_sender))); let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; diff --git a/src/modem.rs b/src/modem.rs index 4594936..db49dd8 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -18,8 +18,18 @@ use esp_idf_hal::serial::{self, Rx, Tx}; use embedded_hal::digital::v2::OutputPin; -use mqtt::packet::{ConnectPacket, PublishPacket, QoSWithPacketIdentifier, VariablePacket}; -use mqtt::{Encodable, Decodable, TopicName}; +use mqtt::{ + Encodable, + Decodable, + TopicName, + packet::{ + ConnectPacket, + PublishPacket, + QoSWithPacketIdentifier, + VariablePacket, + }, +}; +use serde_json_core; pub type Result = std::result::Result; @@ -173,7 +183,7 @@ impl Modem { let _ = self.handle_prompt()?; println!("Handled prompt OK!!"); - println!("Writing bytes in serial TX! ({:?})", String::from_utf8(buf.to_vec())); + println!("Writing bytes in serial TX! ({:?})", buf.into_iter().map(|b| char::from(*b)).collect::()); self.serial .write_bytes(buf) .map_err(|err| ModemError::SendDataError(format!("{:?}", err)))?; @@ -587,7 +597,9 @@ where match receiver.recv() { Ok(Msg::Gps(solution)) => { println!("received GPS solution {:?} | sending to mqtt ...", solution); - let _ = mdm.mqtt_publish(device_id, &format!("{:?}", solution))?; + serde_json_core::ser::to_string::(&solution) + .map_err(|e| anyhow::Error::new(e)) + .and_then(|sol| mdm.mqtt_publish(device_id, &sol))?; err_count = 0; }, Ok(Msg::Accelerometer(acc)) => { diff --git a/src/types.rs b/src/types.rs index ff52839..89c186d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,12 +1,103 @@ #![allow(dead_code)] +use nmea0183::{ + GGA, + GLL, + coords::{ + Latitude as NMEALatitude, + Longitude as NMEALongitude, + Hemisphere as NMEAHemisphere, + }, +}; +use serde::Serialize; -#[derive(Debug)] +#[derive(Debug, Serialize)] +pub enum Hemisphere { + North, + South, + East, + West, +} + +#[derive(Debug, Serialize)] +pub struct Latitude { + degrees: u8, + minutes: u8, + seconds: f32, + hemisphere: Hemisphere, +} + +#[derive(Debug, Serialize)] +pub struct Longitude { + degrees: u8, + minutes: u8, + seconds: f32, + hemisphere: Hemisphere, +} + +impl From for Latitude { + fn from(lat: NMEALatitude) -> Self { + Self { + degrees: lat.degrees, + minutes: lat.minutes, + seconds: lat.seconds, + hemisphere: lat.hemisphere.into(), + } + } +} + +impl From for Longitude { + fn from(lon: NMEALongitude) -> Self { + Self { + degrees: lon.degrees, + minutes: lon.minutes, + seconds: lon.seconds, + hemisphere: lon.hemisphere.into(), + } + } +} + +impl From for Hemisphere { + fn from(hem: NMEAHemisphere) -> Self { + match hem { + NMEAHemisphere::North => Self::North, + NMEAHemisphere::South => Self::South, + NMEAHemisphere::East => Self::East, + NMEAHemisphere::West => Self::West, + } + } +} + +#[derive(Debug, Serialize)] pub struct Solution { - pub latitude: f64, - pub longitude: f64, - pub altitude: f64, - pub speed: f64, - pub direction: f64, + pub latitude: Latitude, + pub longitude: Longitude, + pub altitude: Option, + pub speed: Option, + pub direction: Option, +} + +impl From for Solution { + fn from(gga: GGA) -> Self { + Self { + latitude: gga.latitude.into(), + longitude: gga.longitude.into(), + altitude: Some(gga.altitude.meters), + speed: None, + direction: None, + } + } +} + +impl From for Solution { + fn from(gll: GLL) -> Self { + Self { + latitude: gll.latitude.into(), + longitude: gll.longitude.into(), + altitude: None, + speed: None, + direction: None, + } + } } pub enum Msg { From 9d536bdce9a15f3af25dc70cdf4675b785180889 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 20 Feb 2023 14:55:00 +0100 Subject: [PATCH 29/36] move connecting and sending to func --- src/modem.rs | 108 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/src/modem.rs b/src/modem.rs index db49dd8..4b3b3ce 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -483,7 +483,7 @@ impl Modem { conn.set_user_name(Some(username.to_string())); conn.set_password(Some(password.to_string())); let _ = conn.encode(&mut buf)?; - self.tcp_manual_send(&mut buf).ok(); + let _ = self.tcp_manual_send(&mut buf)?; let reply = self.mqtt_receive_reply()?; println!("mqtt decoded packet: ({:?})", reply); @@ -503,9 +503,7 @@ impl Modem { message.as_bytes(), ); let _ = packet.encode(&mut buf)?; - println!("created mqtt publish packet ... ({})", - std::str::from_utf8(buf.as_slice()).unwrap_or("")); - self.tcp_manual_send(&mut buf).ok(); + self.tcp_manual_send(&mut buf)?; Ok(()) } } @@ -552,8 +550,6 @@ where let mqtt_username = include_str!("../secret/username").trim(); let mqtt_password = include_str!("../secret/password").trim(); - mdm.init(pwrkey, rst, power)?; - // thread::sleep(Duration::from_millis(500)); //println!("setting up client TLS cert"); @@ -564,61 +560,63 @@ where //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; //let _ = mdm.fs_list("C:\\USER\\")?; - let _ = mdm.echo(false); - // Retry 5 times to open a TCP connection, otherwise fail and wait for reboot. - let mut retries = 0; - - loop { + fn start_sending(mdm: &mut Modem, mqtt_username: &str, mqtt_password: &str, receiver: Receiver) -> anyhow::Result<()> { if !mdm.is_gprs_attached()? { let _ = mdm.gprs_attach_ap(crate::config::MTS)?; + let _ = mdm.try_connect_gprs()?; } - if let Ok(()) = mdm.try_connect_gprs() { - // When command AT+CIPQSEND=0, it is in normal sending mode. In this mode, after user - // sends data by AT+CIPSEND, if the server receives TCP data, it will give ACK message - // to module, and the module will respond SEND OK. - let _ = mdm.send("AT+CIPQSEND=0", "OK"); - // Enables getting data from network manually. - let _ = mdm.send("AT+CIPRXGET=1", "OK"); + // When command AT+CIPQSEND=0, it is in normal sending mode. In this mode, after user + // sends data by AT+CIPSEND, if the server receives TCP data, it will give ACK message + // to module, and the module will respond SEND OK. + let _ = mdm.send("AT+CIPQSEND=0", "OK"); + // Enables getting data from network manually. + let _ = mdm.send("AT+CIPRXGET=1", "OK"); - if let Err(_) = mdm.tcp_connect("51.158.66.64", 7887) { - if retries < 5 { - retries += 1; - continue; + for _ in 0..5 { + if let Ok(_) = mdm.tcp_connect("51.158.66.64", 7887) { + break + } + } + + let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; + println!("connecting to MQTT with ({}:{})", mqtt_username, mqtt_password); + let _ = mdm.mqtt_connect(device_id, mqtt_username, mqtt_password)?; + + println!("entering queue receive loop ..."); + let mut err_count = 0; + let _ = loop { + match receiver.recv() { + Ok(Msg::Gps(solution)) => { + println!("received GPS solution {:?} | sending to mqtt ...", solution); + serde_json_core::ser::to_string::(&solution) + .map_err(|e| anyhow::Error::new(e)) + .and_then(|sol| mdm.mqtt_publish(device_id, &sol))?; + err_count = 0; + }, + Ok(Msg::Accelerometer(acc)) => { + println!("received accel {} | sending to mqtt ...", acc); + let _ = mdm.mqtt_publish(device_id, &format!("{:?}", acc))?; + err_count = 0; + } + Err(e) => { + if err_count < 5 { + err_count += 1; + println!("received error {} | NOT sending to mqtt ...", e); + } + else { + break + } } } + }; - let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; - println!("connecting to MQTT with ({}:{})", mqtt_username, mqtt_password); - let _ = mdm.mqtt_connect(device_id, mqtt_username, mqtt_password)?; - - println!("entering queue receive loop ..."); - let mut err_count = 0; - let _ = loop { - match receiver.recv() { - Ok(Msg::Gps(solution)) => { - println!("received GPS solution {:?} | sending to mqtt ...", solution); - serde_json_core::ser::to_string::(&solution) - .map_err(|e| anyhow::Error::new(e)) - .and_then(|sol| mdm.mqtt_publish(device_id, &sol))?; - err_count = 0; - }, - Ok(Msg::Accelerometer(acc)) => { - println!("received accel {} | sending to mqtt ...", acc); - let _ = mdm.mqtt_publish(device_id, &format!("{:?}", acc))?; - err_count = 0; - } - Err(e) => { - if err_count < 5 { - err_count += 1; - println!("received error {} | NOT sending to mqtt ...", e); - } - else { - break - } - } - } - }; - let _ = mdm.tcp_close_connection()?; - } + Ok(()) } + + mdm.init(pwrkey, rst, power)?; + let _ = mdm.echo(false)?; + let _ = start_sending(&mut mdm, mqtt_username, mqtt_password, receiver)?; + let _ = mdm.tcp_close_connection()?; + + Ok(()) } From 7a22f14e1f211e54929a963cb7fa1589402c198c Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 20 Feb 2023 16:35:54 +0100 Subject: [PATCH 30/36] move code around to make the modem restartable --- src/main.rs | 67 ++++++++++++++++++++------ src/modem.rs | 131 +++++++++++++++++---------------------------------- 2 files changed, 95 insertions(+), 103 deletions(-) diff --git a/src/main.rs b/src/main.rs index 948a843..dd62fb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,10 @@ mod gps; use anyhow; use std::{thread::{self, JoinHandle}, time::Duration}; use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use esp_idf_hal::serial::{Pins, config::Config, Serial, UART1, Uart}; +use embedded_hal::digital::v2::OutputPin; + use types::*; fn main() -> anyhow::Result<()> { @@ -19,6 +23,27 @@ fn main() -> anyhow::Result<()> { println!("Rust main thread: {:?}", thread::current()); + let mut threads: Vec>> = vec![]; + + // // Rx/Tx pins for the GPS modem + let gps_rx = dp.pins.gpio32; + let gps_tx = dp.pins.gpio33; + // // UART interface for the GPS modem + let gps_uart = dp.uart2; + + + let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(10); + //let accel_sender = gps_sender.clone(); + + //let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; + threads.push(thread::spawn(move || gps::main(gps_tx, gps_rx, gps_uart, gps_sender))); + thread::sleep(Duration::from_millis(1000)); + //threads.push(thread::spawn(move || accel::main(accel_sender))); + + // ================================== + // MODEM INITIALIZATION AND MAIN LOOP + // ================================== + // LilyGo TTGO T-Call sim800l board serial pins. let modem_rx = dp.pins.gpio26; let modem_tx = dp.pins.gpio27; @@ -29,24 +54,36 @@ fn main() -> anyhow::Result<()> { // UART interface for the GSM modem let modem_uart = dp.uart1; - let mut threads: Vec>> = vec![]; + let serial_pins = Pins { + tx: modem_tx, + rx: modem_rx, + cts: None, + rts: None, + }; - // // Rx/Tx pins for the GPS modem - let gps_rx = dp.pins.gpio32; - let gps_tx = dp.pins.gpio33; - // // UART interface for the GPS modem - let gps_uart = dp.uart2; + let serial: Serial = Serial::new( + modem_uart, + serial_pins, + Config::default().baudrate(Hertz(115200)), + )?; + let (tx, rx) = serial.split(); + type PwrkeyOutput = esp_idf_hal::gpio::Gpio4; + type ResetOutput = esp_idf_hal::gpio::Gpio5; + type PowerOutput = esp_idf_hal::gpio::Gpio23; - let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(1); - //let accel_sender = gps_sender.clone(); + let mut mdm: modem::Modem = modem::Modem::new(tx, rx, modem_pwrkey, modem_rst, modem_power, receiver); - //let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; - threads.push(thread::spawn(move || gps::main(gps_tx, gps_rx, gps_uart, gps_sender))); - thread::sleep(Duration::from_millis(1000)); - //threads.push(thread::spawn(move || accel::main(accel_sender))); + let mqtt_username = include_str!("../secret/username").trim(); + let mqtt_password = include_str!("../secret/password").trim(); - let _ = modem::main(modem_rx, modem_tx, modem_uart, modem_pwrkey, modem_rst, modem_power, receiver)?; - - Ok(()) + loop { + println!("======================= MAIN ======================="); + mdm.init().unwrap_or(()); + let _ = mdm.echo(false).unwrap_or(()); + println!("resetting modem ... "); + println!("======================= MODEM ======================="); + let _ = mdm.start_sending(mqtt_username, mqtt_password).unwrap_or(()); + thread::sleep(Duration::from_millis(1500)); + } } diff --git a/src/modem.rs b/src/modem.rs index 4b3b3ce..c040bf3 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -13,7 +13,6 @@ use std::{ sync::mpsc::Receiver, }; -use esp_idf_hal::prelude::*; use esp_idf_hal::serial::{self, Rx, Tx}; use embedded_hal::digital::v2::OutputPin; @@ -33,10 +32,6 @@ use serde_json_core; pub type Result = std::result::Result; -pub struct Modem { - serial: SerialIO, -} - #[derive(Debug)] pub enum ModemError { CommandError(String), @@ -54,10 +49,22 @@ impl std::fmt::Display for ModemError { } } -impl Modem { - pub fn new(tx: Tx, rx: Rx) -> Self { +pub struct Modem { + serial: SerialIO, + reset: RST, + power: PW, + power_key: PWK, + receiver: Receiver, +} + +impl Modem { + pub fn new(tx: Tx, rx: Rx, mut pwrkey: PWK, mut rst: RST, mut power: PW, receiver: Receiver) -> Self { Self { serial: SerialIO::new(tx, rx), + reset: rst, + power, + power_key: pwrkey, + receiver, } } @@ -76,22 +83,23 @@ impl Modem { /// /// modem::init(modem_pwrkey, modem_rst, modem_power); /// ``` - pub fn init(&mut self, mut pwrkey: impl OutputPin, mut rst: impl OutputPin, mut power: impl OutputPin) -> Result<()> { + pub fn init(&mut self) -> Result<()> { println!("Turning SIM800L on ..."); - power.set_high().map_err(|_| ModemError::SetupError("Error setting POWER to high.".to_string()))?; - rst.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_string()))?; + self.power.set_high().map_err(|_| ModemError::SetupError("Error setting POWER to high.".to_string()))?; + self.reset.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_string()))?; // Pull down PWRKEY for more than 1 second according to manual requirements - pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; + self.power_key.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; thread::sleep(Duration::from_millis(1500)); - pwrkey.set_low().map_err(|_| ModemError::SetupError("Error setting PWRKEY to low.".to_string()))?; + self.power_key.set_low().map_err(|_| ModemError::SetupError("Error setting PWRKEY to low.".to_string()))?; thread::sleep(Duration::from_millis(1000)); - pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; + self.power_key.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; println!("Waiting for sim module to come online ..."); thread::sleep(Duration::from_millis(3000)); for _ in 0..10 { let _ = self.send_command(Command::probe()).unwrap_or("".to_string()); thread::sleep(Duration::from_millis(1000)); } + self.serial.clear(); Ok(()) } @@ -142,6 +150,7 @@ impl Modem { } fn send(&mut self, at_command: &str, contains: &str) -> Result { + self.serial.clear(); println!("-----------------------------------------------------------"); println!("Sending {} ...", at_command); @@ -303,8 +312,8 @@ impl Modem { } pub fn tcp_manual_send(&mut self, buf: &[u8]) -> Result<()> { - thread::sleep(Duration::from_millis(200)); - self.serial.clear(); + thread::sleep(Duration::from_millis(200)); + // self.serial.clear(); self.tcp_manual_send_data(buf) .map(|_| ()) } @@ -360,8 +369,8 @@ impl Modem { } } - pub fn tcp_close_connection(&mut self) -> Result { - self.send_command(Command::tcp_close()) + pub fn tcp_close_connection(&mut self) -> Result<()> { + self.send_command(Command::tcp_close()).map(|_| ()) } pub fn http_post(&mut self, url: &str, token: &str, content: &[u8]) -> Result { @@ -506,96 +515,43 @@ impl Modem { self.tcp_manual_send(&mut buf)?; Ok(()) } -} -impl std::io::Read for Modem { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - self.tcp_receive(buf).map_err(|_| std::io::Error::from(std::io::ErrorKind::ConnectionAborted)) - } -} - -pub fn main -( - rx: PRx, - tx: PTx, - uart: serial::UART1, - pwrkey: DPK, - rst: DR, - power: DP, - receiver: Receiver, -) -> std::result::Result<(), anyhow::Error> -where - PRx: esp_idf_hal::gpio::Pin + esp_idf_hal::gpio::InputPin + esp_idf_hal::gpio::OutputPin, - PTx: esp_idf_hal::gpio::Pin + esp_idf_hal::gpio::InputPin + esp_idf_hal::gpio::OutputPin, - DPK: embedded_hal::digital::v2::OutputPin, - DR: embedded_hal::digital::v2::OutputPin, - DP: embedded_hal::digital::v2::OutputPin, -{ - let serial_pins = serial::Pins { - tx, - rx, - cts: None, - rts: None, - }; - - let serial: serial::Serial = serial::Serial::new( - uart, - serial_pins, - serial::config::Config::default().baudrate(Hertz(115200)), - )?; - - let (tx, rx) = serial.split(); - let mut mdm = Modem::new(tx, rx); - - let mqtt_username = include_str!("../secret/username").trim(); - let mqtt_password = include_str!("../secret/password").trim(); - - // thread::sleep(Duration::from_millis(500)); - - //println!("setting up client TLS cert"); - //let client_cert = include_bytes!("../certs/full-bin.p12"); - //let client_cert_path = "C:\\USER\\fullchain.pem"; - - //let _ = mdm.upload_cert(client_cert_path, client_cert)?; - //let _ = mdm.ssl_set_client_cert(client_cert_path, "t")?; - //let _ = mdm.fs_list("C:\\USER\\")?; - - fn start_sending(mdm: &mut Modem, mqtt_username: &str, mqtt_password: &str, receiver: Receiver) -> anyhow::Result<()> { - if !mdm.is_gprs_attached()? { - let _ = mdm.gprs_attach_ap(crate::config::MTS)?; - let _ = mdm.try_connect_gprs()?; + pub fn start_sending(&mut self, mqtt_username: &str, mqtt_password: &str) -> anyhow::Result<()> { + if !self.is_gprs_attached()? { + let _ = self.gprs_attach_ap(crate::config::MTS)?; + let _ = self.try_connect_gprs()?; } // When command AT+CIPQSEND=0, it is in normal sending mode. In this mode, after user // sends data by AT+CIPSEND, if the server receives TCP data, it will give ACK message // to module, and the module will respond SEND OK. - let _ = mdm.send("AT+CIPQSEND=0", "OK"); + let _ = self.send("AT+CIPQSEND=0", "OK"); // Enables getting data from network manually. - let _ = mdm.send("AT+CIPRXGET=1", "OK"); + let _ = self.send("AT+CIPRXGET=1", "OK"); for _ in 0..5 { - if let Ok(_) = mdm.tcp_connect("51.158.66.64", 7887) { + if let Ok(_) = self.tcp_connect("51.158.66.64", 7887) { break } } let device_id = "c36a72df-5bd6-4f9b-995d-059433bc3267"; println!("connecting to MQTT with ({}:{})", mqtt_username, mqtt_password); - let _ = mdm.mqtt_connect(device_id, mqtt_username, mqtt_password)?; + let _ = self.mqtt_connect(device_id, mqtt_username, mqtt_password)?; println!("entering queue receive loop ..."); let mut err_count = 0; let _ = loop { - match receiver.recv() { + match self.receiver.recv() { Ok(Msg::Gps(solution)) => { println!("received GPS solution {:?} | sending to mqtt ...", solution); serde_json_core::ser::to_string::(&solution) .map_err(|e| anyhow::Error::new(e)) - .and_then(|sol| mdm.mqtt_publish(device_id, &sol))?; + .and_then(|sol| self.mqtt_publish(device_id, &sol))?; err_count = 0; }, Ok(Msg::Accelerometer(acc)) => { println!("received accel {} | sending to mqtt ...", acc); - let _ = mdm.mqtt_publish(device_id, &format!("{:?}", acc))?; + let _ = self.mqtt_publish(device_id, &format!("{:?}", acc))?; err_count = 0; } Err(e) => { @@ -612,11 +568,10 @@ where Ok(()) } - - mdm.init(pwrkey, rst, power)?; - let _ = mdm.echo(false)?; - let _ = start_sending(&mut mdm, mqtt_username, mqtt_password, receiver)?; - let _ = mdm.tcp_close_connection()?; - - Ok(()) +} + +impl std::io::Read for Modem { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.tcp_receive(buf).map_err(|_| std::io::Error::from(std::io::ErrorKind::ConnectionAborted)) + } } From 5e6810cae8d53d4c964ba92be0ecf58882c33dd9 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 20 Feb 2023 17:14:23 +0100 Subject: [PATCH 31/36] keep at most 3 messages in the channel --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index dd62fb4..3baf2a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ fn main() -> anyhow::Result<()> { let gps_uart = dp.uart2; - let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(10); + let (gps_sender, receiver) = std::sync::mpsc::sync_channel::(3); //let accel_sender = gps_sender.clone(); //let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; From c9e80434fe0528ef54676c8b6339e818470f53bf Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 20 Feb 2023 17:24:35 +0100 Subject: [PATCH 32/36] wait more between tcp connect reply reads --- src/modem.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modem.rs b/src/modem.rs index c040bf3..41d95ec 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -280,13 +280,15 @@ impl Modem Result<()> { let at_command = format!("AT+CIPSTART=\"TCP\",\"{}\",\"{}\"", addr, port); - let _ = self.send(&at_command, "CONNECT OK"); + let mut reply_result = self.send(&at_command, "CONNECT OK"); for _ in 0..3 { - if let Ok(reply) = self.command_read_response(Some("CONNECT OK".to_string())) { + if let Ok(reply) = reply_result { println!("TCP connect replied with {}", reply); break + } else { + reply_result = self.command_read_response(Some("CONNECT OK".to_string())); } - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(1000)); } Ok(()) } From d71a7bb2a9f397e8a8993dbf7b357a14b314a5f7 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Mon, 20 Feb 2023 17:29:54 +0100 Subject: [PATCH 33/36] close tcp connection when restarting --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 3baf2a5..6b6f78e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,6 +84,7 @@ fn main() -> anyhow::Result<()> { println!("resetting modem ... "); println!("======================= MODEM ======================="); let _ = mdm.start_sending(mqtt_username, mqtt_password).unwrap_or(()); + let _ = mdm.tcp_close_connection().unwrap_or(()); thread::sleep(Duration::from_millis(1500)); } } From 059263d7eaa8c9594ff5a2a64540f5c19a798091 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sun, 5 Mar 2023 00:01:37 +0100 Subject: [PATCH 34/36] pass mqtt host/port in loop --- src/main.rs | 4 ++-- src/modem.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6b6f78e..e770785 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ fn main() -> anyhow::Result<()> { //let accel_sender = gps_sender.clone(); //let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; - threads.push(thread::spawn(move || gps::main(gps_tx, gps_rx, gps_uart, gps_sender))); + threads.push(thread::spawn(move || gps::main(gps_tx, gps_rx, gps_uart, gps_sender.clone()))); thread::sleep(Duration::from_millis(1000)); //threads.push(thread::spawn(move || accel::main(accel_sender))); @@ -83,7 +83,7 @@ fn main() -> anyhow::Result<()> { let _ = mdm.echo(false).unwrap_or(()); println!("resetting modem ... "); println!("======================= MODEM ======================="); - let _ = mdm.start_sending(mqtt_username, mqtt_password).unwrap_or(()); + let _ = mdm.mqtt_send_position_loop("51.158.66.64", 7887, mqtt_username, mqtt_password).unwrap_or(()); let _ = mdm.tcp_close_connection().unwrap_or(()); thread::sleep(Duration::from_millis(1500)); } diff --git a/src/modem.rs b/src/modem.rs index 41d95ec..953fc1d 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -518,7 +518,7 @@ impl Modem anyhow::Result<()> { + pub fn mqtt_send_position_loop(&mut self, host: &str, port: u16, username: &str, password: &str) -> anyhow::Result<()> { if !self.is_gprs_attached()? { let _ = self.gprs_attach_ap(crate::config::MTS)?; let _ = self.try_connect_gprs()?; @@ -531,14 +531,14 @@ impl Modem Date: Sun, 5 Mar 2023 03:54:32 +0100 Subject: [PATCH 35/36] reboot device (with panic!) closes #4 closes #12 --- src/main.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index e770785..e6b4325 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,8 +36,6 @@ fn main() -> anyhow::Result<()> { //let accel_sender = gps_sender.clone(); //let _ = gps::main(gps_tx, gps_rx, gps_uart, gps_sender)?; - threads.push(thread::spawn(move || gps::main(gps_tx, gps_rx, gps_uart, gps_sender.clone()))); - thread::sleep(Duration::from_millis(1000)); //threads.push(thread::spawn(move || accel::main(accel_sender))); // ================================== @@ -77,14 +75,15 @@ fn main() -> anyhow::Result<()> { let mqtt_username = include_str!("../secret/username").trim(); let mqtt_password = include_str!("../secret/password").trim(); - loop { - println!("======================= MAIN ======================="); - mdm.init().unwrap_or(()); - let _ = mdm.echo(false).unwrap_or(()); - println!("resetting modem ... "); - println!("======================= MODEM ======================="); - let _ = mdm.mqtt_send_position_loop("51.158.66.64", 7887, mqtt_username, mqtt_password).unwrap_or(()); - let _ = mdm.tcp_close_connection().unwrap_or(()); - thread::sleep(Duration::from_millis(1500)); - } + threads.push(thread::spawn(move || gps::main(gps_tx, gps_rx, gps_uart, gps_sender.clone()))); + + println!("======================= MAIN ======================="); + mdm.init().unwrap_or(()); + let _ = mdm.echo(false).unwrap_or(()); + println!("resetting modem ... "); + println!("======================= MODEM ======================="); + let _ = mdm.mqtt_send_position_loop("51.158.66.64", 7887, mqtt_username, mqtt_password).unwrap_or(()); + let _ = mdm.tcp_close_connection().unwrap_or(()); + thread::sleep(Duration::from_millis(1500)); + panic!("rebooting"); } From 97de5e451e680f41e8aaba6f238383d69ce04dc3 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Tue, 16 May 2023 01:29:56 +0200 Subject: [PATCH 36/36] update esp toolchain to [rust] 1.69 --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a2f5ab5..bc9d06e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "esp" +channel = "esp-1.69.0.0"