first commit

This commit is contained in:
aOK 2024-07-22 17:16:42 +03:00
commit fa60f22822
25 changed files with 2835 additions and 0 deletions

14
.cargo/config.toml Normal file
View file

@ -0,0 +1,14 @@
[target.xtensa-esp32-none-elf]
runner = "espflash flash -M -b 2000000 --partition-table partitions.csv -p /dev/cu.usbserial-1420"
[env]
ESP_LOGLEVEL = "INFO"
[build]
rustflags = ["-C", "link-arg=-nostartfiles", "-Dclippy::clone_on_ref_ptr"]
target = "xtensa-esp32-none-elf"
[unstable]
build-std = ["alloc", "core"]

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
*.lock
src/main.rs.txt

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"rust-analyzer.check.allTargets": false,
}

BIN
A9G GPRS Operation.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

108
Cargo.toml Normal file
View file

@ -0,0 +1,108 @@
[package]
name = "ppp-gsm"
version = "0.1.0"
authors = ["aok"]
edition = "2021"
license = "MIT OR Apache-2.0"
[lib]
name = "ppp_gsm"
[dependencies]
esp-backtrace = { version = "0.12.0", features = [
"esp32",
"exception-handler",
"panic-handler",
"println",
] }
hal = { package = "esp-hal", version = "0.19.0", features = [
# "embassy",
"async",
"esp32",
# "embassy-time-timg0",
# "embassy-executor-thread",
] }
esp-hal-embassy = { version = "0.2.0", features = ["esp32", "executors"] }
embassy-time = "0.3.1"
embassy-sync = "0.6.0"
embedded-io = { version = "0.6.1" }
embedded-io-async = "0.6.1"
# embedded-io = "0.6.1"
esp-println = { version = "0.9.1", features = ["esp32", "log"] }
log = { version = "0.4.21" }
heapless = "0.8.0"
esp-alloc = { version = "0.4.0" }
static_cell = { version = "2.1.0", features = ["nightly"] }
embassy-executor = { version = "0.5.0", features = [
"nightly",
"integrated-timers",
] }
bytemuck = { version = "1.16.1", features = ["derive"] }
embedded-storage = "0.3.1"
esp-storage = { version = "0.3.0", features = ["esp32"] }
# embassy-net = { version = "0.4.0", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] }
rand_core = { version = "0.6.3", default-features = false }
# rand_chacha = { version = "0.3.1", default-features = false }
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
embassy-net = { version = "0.4.0", features = [
"log",
"medium-ethernet",
"medium-ip",
"tcp",
"udp",
"dns",
"dhcpv4",
"proto-ipv6",
] }
embassy-net-ppp = { version = "0.1.0", features = ["log"] }
# ppproto = "0.1.2"
# smoltcp = { version = "0.11.0", default-features = false, features = [
# "proto-igmp",
# "proto-ipv4",
# "socket-tcp",
# "socket-icmp",
# "socket-udp",
# "medium-ethernet",
# "proto-dhcpv4",
# "socket-raw",
# "socket-dhcpv4",
# ] }
picoserve = "0.11.1"
embedded-tls = { version = "0.17.0", default-features = false }
rust-mqtt = { version = "0.3.0", default-features = false }
# futures = { version = "0.3.30", default-features = false, features = ["alloc"] }
reqwless = { version = "0.12.1", features = ["alloc", "log"] }
embassy-futures = "0.1.1"
# embedded-tls = "0.17.0"
# rust-mqtt = "0.3.0"
# rust-mqtt = { git = "https://github.com/obabec/rust-mqtt", rev = "483138c", default-features = false }
# defmt-or-log = "0.2.1"
# defmt-or-log = { version = "0.2.1", features = ["defmt"] }
# defmt = "0.3.8"
# defmt-rtt = "0.4.1"
[dev-dependencies]
embassy-time = { version = "0.3.1", features = ["std", "generic-queue"] }
critical-section = { version = "1.1", features = ["std"] }
tokio = { version = "1", features = ["macros", "rt"] }
static_cell = { version = "2.0.0" }
[profile.dev]
# Rust debug is too slow.
# For debug builds always builds with some optimization
opt-level = "s"
[profile.release]
codegen-units = 1 # LLVM can perform better optimizations using a single thread
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 2
overflow-checks = false

201
LICENSE-APACHE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
LICENSE-MIT Normal file
View file

@ -0,0 +1,25 @@
Copyright [year] [fullname]
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

0
README.md Normal file
View file

3
build.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-arg-bins=-Tlinkall.x");
}

6
partitions.csv Normal file
View file

@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
ota_0, app, ota_0, 0x10000, 0x180000,
ota_1, app, ota_1, 0x190000, 0x180000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x4000
3 otadata data ota 0xd000 0x2000
4 phy_init data phy 0xf000 0x1000
5 ota_0 app ota_0 0x10000 0x180000
6 ota_1 app ota_1 0x190000 0x180000

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "esp"

122
src/gps.rs Normal file
View file

@ -0,0 +1,122 @@
#![no_std]
use core::str;
fn get_gps_location(serial: &mut impl Serial) -> Result<(&[u8], &[u8]), ()> {
// Request GPS information
serial.write(b"AT+GPSRD=1\r\n").map_err(|_| ())?;
// Wait for GPS information to be available
delay(2000); // You'll need to implement a suitable delay for your platform
let mut response = [0u8; 128]; // Adjust buffer size as needed
let mut bytes_read = 0;
while let Some(byte) = serial.read().map_err(|_| ())? {
if bytes_read < response.len() {
response[bytes_read] = byte;
bytes_read += 1;
} else {
// Handle buffer overflow if necessary
break;
}
}
// Extract latitude and longitude from the response
let response_slice = &response[..bytes_read];
if let Some(index) = find_subsequence(response_slice, b"GPSRD: ") {
let data = &response_slice[index + 7..];
if let Some(comma_index) = data.iter().position(|&b| b == b',') {
let latitude = &data[..comma_index];
let longitude = &data[comma_index + 1..];
if let Some(end_index) = longitude.iter().position(|&b| b == b',') {
let longitude = &longitude[..end_index];
Ok((latitude, longitude))
} else {
Err(())
}
} else {
Err(())
}
} else {
Err(())
}
}
// Placeholder trait for serial communication
trait Serial {
fn write(&mut self, data: &[u8]) -> Result<(), ()>;
fn read(&mut self) -> Result<Option<u8>, ()>;
}
// Placeholder for delay function
fn delay(_ms: u32) {
// Implement a suitable delay for your platform
}
// Helper function to find a subsequence in a byte slice
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
use bytes::{BufMut, BytesMut};
use core::fmt::Write; // For writing to strings // For byte buffer handling
// Mock implementation of serial communication and delay
struct MySerial;
impl MySerial {
fn available(&self) -> usize {
// Implementation to get number of bytes available in the serial buffer
unimplemented!()
}
fn read(&self) -> u8 {
// Implementation to read a byte from the serial buffer
unimplemented!()
}
}
fn delay(_ms: u32) {
// Implementation of delay
unimplemented!()
}
fn send_at_command(command: &[u8]) {
// Implementation to send command to the GPS module
unimplemented!()
}
fn get_gps_location(my_serial: &MySerial) -> Option<(BytesMut, BytesMut)> {
// Request GPS information
send_at_command(b"AT+GPSRD=1\r\n");
// Wait for GPS information to be available
delay(2000);
let mut response = BytesMut::with_capacity(128); // Pre-allocate buffer
// Read available bytes from the serial
while my_serial.available() > 0 {
let byte = my_serial.read();
response.put_u8(byte); // Store the byte in the buffer
}
// Parse the response for latitude and longitude
if let Some(index) = response.windows(7).position(|w| w == b"GPSRD: ") {
let trimmed_response = &response[index + 8..]; // Skip "GPSRD: "
if let Some(comma_index) = trimmed_response.iter().position(|&b| b == b',') {
let latitude = trimmed_response[..comma_index].to_vec().into();
let remaining = &trimmed_response[comma_index + 1..];
if let Some(next_comma_index) = remaining.iter().position(|&b| b == b',') {
let longitude = remaining[..next_comma_index].to_vec().into();
return Some((latitude, longitude));
}
}
}
None // Return None if parsing fails
}

85
src/lib.rs Normal file
View file

@ -0,0 +1,85 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![deny(clippy::clone_on_ref_ptr)]
// #[macro_use]
extern crate alloc;
pub mod run;
pub mod utils;
// use embassy_executor::Spawner;
// use embassy_net::tcp::TcpSocket;
// use embassy_net::{Stack, StackResources};
// use embassy_serial::{AsyncDevice, Config};
// use embassy_time::{Duration, Timer};
// use embedded_hal::digital::v2::OutputPin;
// use std::net::Ipv4Addr;
// #[embassy_executor::main]
// async fn main(_spawner: Spawner) {
// // Configure the serial port connected to the ESP32
// let config = Config::default();
// let mut serial = AsyncDevice::new(peripherals.UART0, config).unwrap();
// // Configure the network stack
// static mut RX_BUFFER: [u8; 1024] = [0; 1024];
// static mut TX_BUFFER: [u8; 1024] = [0; 1024];
// let stack = &mut Stack::new(
// StackResources::new()
// .tx_buffer(TX_BUFFER)
// .rx_buffer(RX_BUFFER),
// );
// // Power on the modem (if necessary)
// // ...
// // Initialize the PPP connection
// let mut ppp = embassy_net_ppp::Ppp::new(&mut serial, stack);
// // Attach to the GPRS network
// ppp.write_at("AT+CGATT=1\r").await.unwrap();
// ppp.wait_for_ok().await.unwrap();
// // Set the PDP context
// ppp.write_at("AT+CGDCONT=1,\"IP\",\"cmnet\"\r")
// .await
// .unwrap();
// ppp.wait_for_ok().await.unwrap();
// // AT+CGDCONT=1,"IP","internet"
// // Activate the PDP context
// ppp.write_at("AT+CGACT=1,1\r").await.unwrap();
// ppp.wait_for_ok().await.unwrap();
// // Start PPP translation
// ppp.write_at("ATD*99***1#\r").await.unwrap();
// ppp.wait_for("CONNECT").await.unwrap();
// // Wait for the connection to be established
// ppp.wait_for("+++").await.unwrap();
// Timer::after(Duration::from_secs(3)).await;
// // Configure the network interface
// let config = embassy_net::Config::dhcpv4(Default::default());
// stack.configure(config).unwrap();
// // Wait for the network to be configured
// stack.wait_for_link().await;
// // Get the assigned IP address
// let ip = stack.get_ip_address().unwrap();
// println!("IP address: {}", ip);
// // Create a TCP socket
// let mut socket = TcpSocket::new(stack, &mut [0; 1024]);
// socket.bind(Ipv4Addr::UNSPECIFIED, 8080).unwrap();
// socket.listen(1).unwrap();
// loop {
// // Accept a connection
// let (_remote_addr, mut connection) = socket.accept().await.unwrap();
// // Handle the connection
// // ...
// }
// }

317
src/main.rs Normal file
View file

@ -0,0 +1,317 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![deny(clippy::clone_on_ref_ptr)]
use alloc::vec::Vec;
use embassy_executor::Spawner;
use embassy_net::{Config, ConfigV4, Ipv4Address, Ipv4Cidr, Stack, StackResources};
use embassy_net_ppp::Runner;
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex, signal::Signal};
use embassy_time::{with_timeout, Duration, Timer};
use esp_backtrace as _;
// use hal::uart::ClockSource;
use hal::{
clock::{ClockControl, Clocks},
delay::Delay,
gpio::{GpioPin, Io},
peripherals::{Peripherals, UART0, UART1},
prelude::*,
system::SystemControl,
timer::{timg::TimerGroup, ErasedTimer, OneShotTimer},
uart::{
config::{Config as UartConfig, DataBits, Parity, StopBits},
ClockSource, Uart,
},
};
use log::{error, info, warn};
use ppp_gsm::{
run::modem::{
module::{SharedUart1, UartWrapper},
parse::parse::{ModemResponse, ToSend},
},
singleton,
utils::generate_random_seed,
};
use static_cell::StaticCell;
extern crate alloc;
use core::mem::MaybeUninit;
#[global_allocator]
static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();
fn init_heap() {
const HEAP_SIZE: usize = 32 * 1024;
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();
unsafe {
ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE);
}
}
pub const READ_BUF_SIZE: usize = 64;
// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write(($val));
x
}};
}
#[main]
async fn main(spawner: Spawner) {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
// let clocks = ClockControl::max(system.clock_control).freeze();
let clocks = singleton!(ClockControl::max(system.clock_control).freeze(), Clocks<'_>);
let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks, None);
let timer0 = OneShotTimer::new(timg0.timer0.into());
let timers = [timer0];
let timers = mk_static!([OneShotTimer<ErasedTimer>; 1], timers);
esp_hal_embassy::init(&clocks, timers);
let delay = Delay::new(&clocks);
init_heap();
esp_println::logger::init_logger_from_env();
// Init network device
static STATE: StaticCell<embassy_net_ppp::State<4, 4>> = StaticCell::new();
let state = STATE.init(embassy_net_ppp::State::<4, 4>::new());
let (device, runner) = embassy_net_ppp::new(state);
// Generate random seed
let seed = generate_random_seed();
// Init network stack
static STACK: StaticCell<Stack<embassy_net_ppp::Device<'static>>> = StaticCell::new();
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
let stack = &*STACK.init(Stack::new(
device,
Config::default(), // don't configure IP yet
RESOURCES.init(StackResources::<3>::new()),
seed,
));
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
static SIGNAL: StaticCell<Signal<NoopRawMutex, (usize, Vec<u8>)>> = StaticCell::new();
let signal = &*SIGNAL.init(Signal::new());
static MODEM_MUTEX: StaticCell<Mutex<NoopRawMutex, UartWrapper<'static>>> = StaticCell::new();
let mut modem = UartWrapper::new(
peripherals.UART0,
peripherals.UART1,
io.pins.gpio14,
io.pins.gpio15,
signal,
&clocks,
);
let modem = &*MODEM_MUTEX.init(Mutex::new(modem));
// spawner.spawn(uart_reader(modem)).unwrap();
match spawner.spawn(uart_reader(modem)) {
Ok(_) => info!("uart_reader task spawned successfully"),
Err(e) => error!("Failed to spawn uart_reader task: {:?}", e),
}
// spawner.spawn(ppp_task(stack, runner, modem)).unwrap();
match spawner.spawn(ppp_task(stack, runner, modem)) {
Ok(_) => info!("ppp_task task spawned successfully"),
Err(e) => error!("Failed to spawn ppp_task task: {:?}", e),
}
{
let mut modem_guard = modem.lock().await;
match modem_guard.init_with_retry().await {
Ok(_) => info!("init completed successfully"),
Err(e) => {
error!("init failure: {:?}", e);
}
};
}
loop {
log::info!("Hello world!");
delay.delay(500.millis());
}
}
use embedded_io_async::Read;
#[embassy_executor::task]
async fn ppp_task(
stack: &'static Stack<embassy_net_ppp::Device<'static>>,
mut runner: Runner<'static>,
// mut modem: UartWrapper<'static>,
modem: &'static Mutex<NoopRawMutex, UartWrapper<'static>>,
) -> ! {
let config = embassy_net_ppp::Config {
username: b"myuser",
password: b"mypass",
};
Timer::after(Duration::from_millis(10)).await; // Adjust delay if needed
let mut modem_guard = modem.lock().await;
runner
.run(&mut *modem_guard, config, |ipv4| {
let Some(addr) = ipv4.address else {
warn!("PPP did not provide an IP address.");
return;
};
// let mut dns_servers = Vec::new();
let mut dns_servers: heapless::Vec<Ipv4Address, 3> = heapless::Vec::new();
for s in ipv4.dns_servers.iter().flatten() {
let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0));
}
let config = ConfigV4::Static(embassy_net::StaticConfigV4 {
address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0),
gateway: None,
dns_servers,
});
stack.set_config_v4(config);
})
.await
.unwrap();
unreachable!()
}
#[embassy_executor::task]
// async fn uart_reader(reader: &'static Mutex<NoopRawMutex, UartWrapper<'static>>) -> ! {
async fn uart_reader(modem: &'static Mutex<NoopRawMutex, UartWrapper<'static>>) -> ! {
const MAX_BUFFER_SIZE: usize = 10 * READ_BUF_SIZE + 16;
let mut rbuf: [u8; MAX_BUFFER_SIZE] = [0u8; MAX_BUFFER_SIZE];
let mut offset = 0;
info!("uart_reader task started");
loop {
info!("reader after loop");
{
let mut modem_guard = modem.lock().await;
info!("rdr after loop");
// Log UART configuration
// info!("UART1 configuration: {:?}", modem_guard.uart1.get_config());
// Perform a direct write-read test to ensure UART is working
use embedded_io_async::Write;
let test_data = b"AT\r\n";
modem_guard.write(test_data).await.unwrap();
info!("Sent test data: {:?}", test_data);
info!("rdr before read()");
match modem_guard.read(&mut rbuf[offset..]).await {
Ok(len) => {
info!("rdr_guard after read()");
if len > 0 {
offset += len;
info!("Received {} bytes: {:?}", len, &rbuf[..offset]);
modem_guard.signal.signal((offset, rbuf[..offset].to_vec()));
offset = 0; // Reset the offset after signaling
} else {
info!("Received empty data (len = 0)");
}
}
Err(e) => {
error!("[uart_reader] RX Error: {:?}", e);
}
}
}
// Add a small delay to avoid busy looping too fast
Timer::after(Duration::from_millis(1)).await; // Adjust delay if needed
}
}
trait NewTrait<'a> {
pub async fn send_at(
&mut self,
tosend: Option<&[u8]>,
msg: Option<&str>,
_timeout_ms: Option<u64>,
);
fn new(
// uart: &'a mut Uart<'static, UART1, Async>
_uart0_per: UART0,
uart1_per: UART1,
tx_pin: GpioPin<14>,
rx_pin: GpioPin<15>,
signal: &'static Signal<NoopRawMutex, (usize, Vec<u8>)>,
clocks: &Clocks<'static>,
) -> Self;
}
impl<'a> NewTrait<'a> for UartWrapper<'a> {
fn new(
// uart: &'a mut Uart<'static, UART1, Async>
_uart0_per: UART0,
uart1_per: UART1,
tx_pin: GpioPin<14>,
rx_pin: GpioPin<15>,
signal: &'static Signal<NoopRawMutex, (usize, Vec<u8>)>,
clocks: &Clocks<'static>,
) -> Self {
// let uart1 = {
let (tx, rx) = {
let config = UartConfig {
baudrate: 115200,
data_bits: DataBits::DataBits8,
parity: Parity::ParityNone,
stop_bits: StopBits::STOP1,
clock_source: ClockSource::Apb,
rx_fifo_full_threshold: 1,
rx_timeout: Some(3),
};
config.rx_fifo_full_threshold(1);
let uart =
Uart::new_async_with_config(uart1_per, config, &clocks, tx_pin, rx_pin).unwrap();
uart.split()
};
let modem_res = ModemResponse::new();
let tosend = ToSend::new();
Self {
uart1: SharedUart1 { tx, rx },
signal,
buffer: [0; 256],
read_pos: 0,
write_pos: 0,
modem_res,
tosend,
}
}
async fn send_at(
&mut self,
tosend: Option<&[u8]>,
msg: Option<&str>,
_timeout_ms: Option<u64>,
) {
let msg_str = msg.unwrap_or("default");
let tosend_f = tosend.unwrap_or(AT_CMD);
info!(
"\n\x1b[32m[{msg_str}]\x1b[0m writing command: {:?}",
tosend_f
);
// Set the command to be sent
self.tosend.set_to_sent(tosend_f);
// Get the command after setting it
let cmd = self.tosend.get_to_sent().to_vec(); // Clone the command to a local variable
if cmd.is_empty() {
info!("EMPTY: Command to send to modem is empty");
return;
}
use embedded_io::Write;
// Now we can safely call self.write without borrowing self.tosend
if let Err(e) = self.write(&cmd).await {
error!("UART1 write failure: {:?}", e);
} else {
info!("UART1 wrote [{:?}] successfully", cmd);
}
}
}

1
src/run/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod modem;

82
src/run/modem/apn.rs Normal file
View file

@ -0,0 +1,82 @@
// use super::{
// errors::{Error, Result},
// module::{FoundResponse, Modem},
// parse::parse::{ModemResponse, Status},
// };
use super::{
error::{Error, Result},
module::FoundResponse,
};
#[derive(Clone)]
pub struct Apn<'a> {
pub apn: &'a str,
pub username: &'a str,
pub password: &'a str,
}
impl<'a> Apn<'a> {
pub const fn new(apn: &'a str) -> Self {
Self {
apn,
username: "",
password: "",
}
}
pub fn get_apn_field(&self) -> &'a str {
self.apn
}
}
impl<'a> From<&'a str> for Apn<'a> {
fn from(value: &'a str) -> Self {
Apn::new(value)
}
}
impl UartWrapper {
pub async fn get_apn(&mut self) -> Result<Apn> {
self.tosend.set_to_sent(b"AT+CSTT?\r\n");
self.send_at(None, Some("get_apn"), None).await;
let response = self.wait_response(None, None, None).await?;
match response.1 {
FoundResponse::M1 => Ok(self.modem_res.parse_apn(&response.0.response.data[..])?),
_ => Err(Error::Custom("error getting apn")),
}
}
pub async fn set_apn(
&mut self,
provider_name: &str,
userid: &str,
password: &str,
) -> Result<Status> {
self.tosend.set_to_sent(
alloc::format!(
"AT+CSTT=\"{}\",\"{}\",\"{}\"\r\n",
provider_name,
userid,
password
)
.as_bytes(),
);
self.send_at(None, Some("set_apn"), None).await;
let response = self.wait_response(None, None, None).await?;
match response.1 {
FoundResponse::M1 => Ok(response.0.response.status),
_ => Err(Error::Custom("error setting apn")),
}
}
}
impl ModemResponse {
pub(crate) fn parse_apn(&self, apn_response: &[u8]) -> Result<Apn> {
// +CSTT: \"CMNET\", \"\", \"\""
let apn = Apn::new("internet");
Ok(apn)
}
}

View file

@ -0,0 +1,435 @@
use log::{error, info};
use crate::run::modem::{
errors::{Error, Result},
module::{FoundResponse, RegistrationStatus, UartWrapper},
parse::parse::{ModemResponse, PdpState, Status},
};
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum CIPSTATUS {
IPINITIAL,
IPSTART,
IPCONFIG,
IPIND,
IPGPRSACT,
IPSTATUS,
TCPCONNECTING,
IPCLOSE,
CONNECTOK,
}
impl<'a> UartWrapper<'a> {
/*
AT+CGATT=1 ;Attach to the GPRS network, can also use parameter 0 to detach.
OK ;Response, attach successful
AT+CGDCONT=? ;Input test command for help information.
+CGDCONT: (1..7), (IP,IPV6,PPP),(0..3),(0..4) OK ;Response, show the helpful information.
// AT+CGDCONT=1,"IP","internet"
AT+CGDCONT=1, "IP", "cmnet" ;Before active, use this command to set PDP context.
OK ;Response. Set context OK.
AT+CGACT=1,1 ;Active command is used to active the specified PDP context.
OK ;Response, active successful.
ATD*99***1# ;This command is to start PPP translation.
CONNECT ;Response, when get this, the module has been set to data state.
PPP data should be transferred after this response and anything input is treated as data.
+++ ;This command is to change the status to online data state.
Notice that before input this command, you need to wait for a
three seconds break, and it should also be followed by 3 seconds
break, otherwise +++ will be treated as data.
ATH ;Use this command to return COMMAND state
ok Response
*/
pub async fn set_modem_gprs(&mut self) -> Result<(CIPSTATUS, Status)> {
// Attempt to check network registration, handle potential errors
self.check_network_registration().await.map_err(|e| {
info!("Failed to check network registration: {:?}", e);
Error::Custom("Network registration failed")
})?;
// Check attached network status
match self.check_attached_network().await {
Ok(s) => match s {
PdpState::Activated => {
info!("Already attached to the network");
let pdpstatus = self.set_pdp_parameter().await.map_err(|e| {
error!("Failed to set PDP parameter: {:?}", e);
Error::Custom("Setting PDP parameter failed")
})?;
match pdpstatus {
Status::OK => {
info!("PDP set");
let pdpactivationstatus =
self.activating_pdp_context().await.map_err(|e| {
error!("Failed to activate PDP context: {:?}", e);
Error::Custom("Activating PDP context failed")
})?;
match pdpactivationstatus {
Status::OK => {
info!("PDP context activated");
let cipstatus = self.check_ip_status().await.map_err(|e| {
error!("Failed to check IP status: {:?}", e);
Error::Custom("Checking IP status failed")
})?;
Ok((cipstatus, pdpactivationstatus))
}
Status::ModemError(e) => {
error!("Modem responded with an error: {:?}", e);
Err(Error::Custom("Modem error during PDP context activation"))
}
_ => Err(Error::Custom("Unexpected status")),
}
}
Status::ModemError(e) => {
error!("Modem responded with an error: {:?}", e);
Err(Error::Custom("Modem error during PDP parameter setting"))
}
_ => Err(Error::Custom("Unexpected status")),
}
}
PdpState::Deactivated => {
let status = self.attach_network().await.map_err(|e| {
error!("Failed to attach network: {:?}", e);
Error::Custom("Attaching to network failed")
})?;
match status {
Status::OK => {
info!("Attached to the network");
let pdpstatus = self.set_pdp_parameter().await.map_err(|e| {
error!("Failed to set PDP parameter: {:?}", e);
Error::Custom("Setting PDP parameter failed")
})?;
match pdpstatus {
Status::OK => {
info!("PDP set");
let pdpactivationstatus =
self.activating_pdp_context().await.map_err(|e| {
error!("Failed to activate PDP context: {:?}", e);
Error::Custom("Activating PDP context failed")
})?;
match pdpactivationstatus {
Status::OK => {
info!("PDP context activated");
let cipstatus =
self.check_ip_status().await.map_err(|e| {
error!("Failed to check IP status: {:?}", e);
Error::Custom("Checking IP status failed")
})?;
Ok((cipstatus, pdpactivationstatus))
}
Status::ModemError(e) => {
error!("Modem responded with an error: {:?}", e);
Err(Error::Custom(
"Modem error during PDP context activation",
))
}
// Status::CONNECT => {} //
_ => Err(Error::Custom("Unexpected status")),
}
}
Status::ModemError(e) => {
info!("Modem responded with an error: {:?}", e);
Err(Error::Custom("Modem error during PDP parameter setting"))
}
_ => Err(Error::Custom("Unexpected status")),
}
}
Status::ModemError(e) => {
error!("Modem responded with an error: {:?}", e);
Err(Error::Custom("Modem error during network attachment"))
}
_ => Err(Error::Custom("Unexpected status")),
}
}
},
Err(e) => {
error!("Failed to check attached network: {:?}", e);
Err(Error::Custom("Checking attached network failed"))
}
}
}
pub async fn check_ip_status(&mut self) -> Result<CIPSTATUS> {
// AT+CIPSTATUS
// self.tosend.set_to_sent(b"AT+CIPSTATUS\r\n");
// self.send_at(None, Some("check_ip_status"), None).await;
// let response = self.wait_response(None, None, None).await?;
let response = {
let cmd = b"AT+CIPSTATUS\r\n";
self.send_at(Some(cmd), Some("check_ip_status"), None).await;
let res_prefix = b"AT+CIPSTATUS\r\n+CIPSTATUS:\r\nSTATE:";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => Ok(self
.modem_res
.parse_ip_status(&response.0.response.data[..])?),
_ => Err(Error::Custom("Failed to check IP status")),
}
}
/// Returns the attach network of this [`Modem`].
/// Attach to the GPRS network, can also use parameter 0 to detach.
/// Activate GPRS - "AT+CGATT=1"
pub async fn attach_network(&mut self) -> Result<Status> {
// self.tosend.set_to_sent(b"AT+CGATT=1\r\n");
// self.send_at(None, Some("attach_network"), None).await;
// let response = self.wait_response(None, None, None).await?;
let response = {
let cmd = b"AT+CGATT=1\r\n";
self.send_at(Some(cmd), Some("attach_network"), None).await;
let res_prefix = b"AT+CGATT=1\r\n";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => {
let status = response.0.response.status;
Ok(status)
}
_ => Err(Error::Custom("failed to attach to network")),
}
}
pub async fn start_ppp(&mut self) -> Result<Status> {
// self.tosend.set_to_sent(b"AT+CGATT=1\r\n");
// self.send_at(None, Some("attach_network"), None).await;
// let response = self.wait_response(None, None, None).await?;
let response = {
let cmd = b"ATD*99***1#\r\n";
self.send_at(Some(cmd), Some("start_ppp"), None).await;
let res_prefix = b"ATD*99***1#\r\n";
let res_suffix = b"\r\n\r\nCONNECT\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => {
let status = response.0.response.status;
Ok(status)
}
_ => Err(Error::Custom("failed to attach to network")),
}
}
/// Command: AT+CGDCONT=?
/// Response: +CGDCONT: (1..7), (IP,IPV6,PPP),(0..3),(0..4)
/// Returns the read pdp context of this [`Modem`].
/// Input test command for help information.
pub async fn read_pdp_context(&mut self) -> Result<Status> {
// self.tosend.set_to_sent(b"AT+CGDCONT=?\r\n");
// self.send_at(None, Some("read_pdp_context"), None).await;
// let response = self.wait_response(None, None, None).await?;
let response = {
let cmd = b"AT+CGDCONT=?\r\n";
self.send_at(Some(cmd), Some("read_pdp_context"), None)
.await;
let res_prefix = b"AT+CGDCONT=?\r\n\r\n+CGDCONT: ";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => {
let status = response.0.response.status;
Ok(status)
}
_ => Err(Error::Custom("failed to read pdp context")),
}
}
pub async fn set_pdp_parameter(&mut self) -> Result<Status> {
// "AT+CGDCONT=1,\"IP\",\"CMNET\""
// "AT+CGDCONT=1,\"IP\",\"internet\""
// AT+CGDCONT=?
// self.tosend
// .set_to_sent(b"AT+CGDCONT=1,\"IP\",\"CMNET\"\r\n");
// self.send_at(None, Some("set_pdp_parameter"), None).await;
// let response = self.wait_response(None, None, None).await?;
let response = {
let cmd = b"AT+CGDCONT=1,\"IP\",\"CMNET\"\r\n";
self.send_at(Some(cmd), Some("set_pdp_parameter"), None)
.await;
let res_prefix = b"AT+CGDCONT=1,\"IP\",\"CMNET\"\r\n";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => {
let status = response.0.response.status;
Ok(status)
}
_ => Err(Error::Custom("failed to set pdp parameter")),
}
}
pub async fn activating_pdp_context(&mut self) -> Result<Status> {
// Activate PDP context - "AT+CGACT=1,1"
// self.tosend.set_to_sent(b"AT+CGACT=1,1\r\n");
// self.send_at(None, Some("activating_pdp_context"), None)
// .await;
// let response = self.wait_response(None, None, None).await?;
let response = {
let cmd = b"AT+CGACT=1,1\r\n";
self.send_at(Some(cmd), Some("activating_pdp_context"), None)
.await;
let res_prefix = b"AT+CGACT=1,1\r\n";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => {
let status = response.0.response.status;
Ok(status)
}
_ => Err(Error::Custom("Failed to activate PDP context")),
}
}
pub async fn check_attached_network(&mut self) -> Result<PdpState> {
// get_pdp_context_states
// Activate GPRS - "AT+CGACT=?"
// self.tosend.set_to_sent(b"AT+CGACT=?\r\n");
// self.send_at(None, Some("check_attached_network"), None)
// .await;
// let response = self.wait_response(None, None, None).await?;
let response = {
let cmd = b"AT+CGACT=?\r\n";
self.send_at(Some(cmd), Some("check_attached_network"), None)
.await;
let res_prefix = b"AT+CGACT=?\r\n\r\n+CGACT: ";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => Ok(self
.modem_res
.get_pdp_context_states(&response.0.response.data[..])?),
_ => Err(Error::Custom("Failed to activate GPRS")),
}
}
/// Returns the get gprs network registration status of this [`Modem`].
/// GPRS network registration status
pub async fn get_gprs_network_registration_status(
&mut self,
) -> Result<(RegistrationStatus, Status)> {
// let response = {
// // self.send_at("+CGREG?", None).await?;
// self.tosend.set_to_sent(b"AT+CGREG?\r\n");
// self.send_at(None, Some("get_gprs_network_registration_status"), None)
// .await;
// let response = self.wait_response(None, None, None).await?;
// };
let response = {
let cmd = b"AT+CGREG?";
self.send_at(Some(cmd), Some("gprs_network"), None).await;
let res_prefix = b"AT+CGREG?\r\n\r\n+CGREG: ";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => Ok(self
.modem_res
.parse_registration_status(&response.0.response.data[..])?),
// FoundResponse::M1 => Ok(&response.0),
_ => Err(Error::Custom("error getting registration status")),
}
}
}
impl ModemResponse {
const fn cipstatus_from_repr(d: &[u8]) -> Option<CIPSTATUS> {
match d {
b"IP INITIAL" => Some(CIPSTATUS::IPINITIAL),
b"CONNECT OK" => Some(CIPSTATUS::CONNECTOK),
b"IP START" => Some(CIPSTATUS::IPSTART),
b"IP CONFIG" => Some(CIPSTATUS::IPCONFIG),
b"IP IND" => Some(CIPSTATUS::IPIND),
b"IP GPRSACT" => Some(CIPSTATUS::IPGPRSACT),
b"TCP CONNECTING" => Some(CIPSTATUS::TCPCONNECTING),
b"IP CLOSE" => Some(CIPSTATUS::IPCLOSE),
_ => None,
}
}
pub(crate) fn parse_ip_status(&self, cipstatus_response: &[u8]) -> Result<CIPSTATUS> {
// "AT+CIPSTATUS\r\n+CIPSTATUS:\r\nSTATE:IP INITIAL \r\n\r\nOK\r\n"
// "+CIPSTATUS:\r\nSTATE:IP INITIAL "
let prefix = b"+CIPSTATUS:\r\nSTATE:";
// Check if the response starts with the expected prefix
if !cipstatus_response.starts_with(prefix) {
return Err(Error::Custom("no matches in parse ip status"));
}
// Remove the prefix by skipping its length
let status_content = &cipstatus_response[prefix.len()..];
// const IPINITIAL: &[u8] = b"IP INITIAL";
// const CONNECTOK: &[u8] = b"CONNECT OK";
// const CONNECTOK: &[u8] = b"IP CLOSE";
match Self::cipstatus_from_repr(status_content) {
Some(cip) => Ok(cip),
None => Err(Error::Custom("unknown cipstatus")),
}
}
pub(crate) fn parse_ip_status_old(&self, cipstatus_response: &[u8]) -> Result<u8> {
// "AT+CIPSTATUS\r\n+CIPSTATUS:\r\nSTATE:IP INITIAL \r\n\r\nOK\r\n"
// "+CIPSTATUS:\r\nSTATE:IP INITIAL "
let prefix = b"+CIPSTATUS:\r\nSTATE:";
// Check if the response starts with the expected prefix
if !cipstatus_response.starts_with(prefix) {
return Err(Error::Custom("no matches in parse ip status"));
}
// Remove the prefix by skipping its length
let status_content = &cipstatus_response[prefix.len()..];
// Find the position of the `:IP` substring
match status_content
.windows(b":IP".len())
.position(|window| window == b":IP")
{
Some(ip_start) => {
// Check if a number exists before the `:IP` substring
if status_content[..ip_start]
.iter()
.rposition(|&b| b.is_ascii_digit())
.is_none()
{
return Err(Error::NoNumberExist);
}
// Extract the number before the `:IP` substring
match status_content[..ip_start]
.iter()
.rposition(|&b| b.is_ascii_digit())
{
Some(number_start) => {
let num_str =
core::str::from_utf8(&status_content[number_start..number_start + 1])
.map_err(|_| Error::ParseError)?;
let number: u8 = num_str.parse().map_err(|_| Error::InvalidNumber)?;
if number <= 7 {
return Ok(number);
} else {
return Err(Error::InvalidNumber);
}
}
None => return Err(Error::NoNumberExist),
}
}
None => return Err(Error::NoNumberExist),
}
}
}

View file

@ -0,0 +1 @@
pub mod gprs;

14
src/run/modem/errors.rs Normal file
View file

@ -0,0 +1,14 @@
#[derive(Debug)]
pub enum Error {
Custom(&'static str),
ParseError,
BorrowError,
InvalidState,
InvalidNumber,
NoNumberExist,
InitTimeout,
Timeout,
UartError(),
}
pub type Result<T> = core::result::Result<T, Error>;

44
src/run/modem/impls.rs Normal file
View file

@ -0,0 +1,44 @@
use embedded_io_async::{BufRead, ErrorType, Read, Write};
use super::module::UartWrapper;
impl<'a> ErrorType for UartWrapper<'a> {
type Error = hal::uart::Error;
}
impl<'a> Read for UartWrapper<'a> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
let available = self.write_pos - self.read_pos;
let to_copy = available.min(buf.len());
buf[..to_copy].copy_from_slice(&self.buffer[self.read_pos..self.read_pos + to_copy]);
self.read_pos += to_copy;
if to_copy == 0 {
// Buffer is empty, read directly from UART
self.uart1.rx.read(buf).await
} else {
Ok(to_copy)
}
}
}
impl<'a> Write for UartWrapper<'a> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.uart1.tx.write(buf).await
}
}
impl<'a> BufRead for UartWrapper<'a> {
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
if self.read_pos == self.write_pos {
// Buffer is empty, fill it
self.read_pos = 0;
self.write_pos = self.uart1.rx.read(&mut self.buffer).await?;
}
Ok(&self.buffer[self.read_pos..self.write_pos])
}
fn consume(&mut self, amt: usize) {
self.read_pos = (self.read_pos + amt).min(self.write_pos);
}
}

6
src/run/modem/mod.rs Normal file
View file

@ -0,0 +1,6 @@
// pub mod apn;
pub mod coms;
pub mod errors;
pub mod impls;
pub mod module;
pub mod parse;

300
src/run/modem/module.rs Normal file
View file

@ -0,0 +1,300 @@
use crate::singleton;
use super::{
errors::{Error, Result},
parse::parse::{ModemResponse, Status, ToSend},
};
use alloc::{
// rc::Rc,
string::ToString,
vec::Vec,
};
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, signal::Signal};
use embassy_time::{with_timeout, Duration, Instant, Timer};
use embedded_io_async::Write;
use hal::{
clock::Clocks,
gpio::GpioPin,
peripherals::{UART0, UART1},
uart::{
config::{Config, DataBits, Parity, StopBits},
ClockSource, Uart, UartRx, UartTx,
},
Async,
};
use log::{error, info, warn};
// uart::{
// config::{Config as UartConfig, DataBits, Parity, StopBits},
// ClockSource, TxRxPins, Uart, UartTx,
// },
// use static_cell::StaticCell;
pub struct SharedUart1<'a> {
pub tx: UartTx<'a, UART1, Async>,
pub rx: UartRx<'a, UART1, Async>,
}
pub struct UartWrapper<'a> {
pub uart1: SharedUart1<'a>,
// pub uart1: Uart<'a, UART1, Async>,
pub signal: &'a Signal<NoopRawMutex, (usize, Vec<u8>)>,
pub buffer: [u8; 256], // Adjust size as needed
pub read_pos: usize,
pub write_pos: usize,
pub modem_res: ModemResponse,
pub tosend: ToSend,
}
impl<'a> UartWrapper<'a> {
pub async fn init_with_retry(&mut self) -> Result<()> {
for attempt in 1..=3 {
info!("Init attempt {}", attempt);
match with_timeout(Duration::from_secs(10), self.init(None)).await {
Ok(result) => return result,
Err(_) => warn!("Init attempt {} timed out", attempt),
}
}
Err(Error::InitTimeout)
}
// async fn wait_for_response(&mut self, timeout: Duration) -> Result<Vec<u8>> {
// let mut buffer = Vec::new();
// let deadline = Timer::after(timeout);
// // let mut modem_guard = modem.lock().await;
// loop {
// match embassy_futures::select::select(self.uart1.read(), deadline).await {
// embassy_futures::select::Either::First(Ok(byte)) => {
// buffer.push(byte);
// if buffer.ends_with(b"\r\nOK\r\n") {
// return Ok(buffer);
// }
// }
// embassy_futures::select::Either::First(Err(e)) => return Err(Error::UartError(e)),
// embassy_futures::select::Either::Second(_) => return Err(Error::Timeout),
// }
// }
// }
pub async fn init(&mut self, timeout_ms: Option<u64>) -> Result<()> {
let timeout = timeout_ms.unwrap_or(30000);
let start_time = Instant::now();
loop {
info!("Modem: init function!");
// Send a simple AT command
let (_, found_response) = {
let cmd = b"AT\r\n";
self.send_at(Some(cmd), Some("init"), None).await;
let isok_res_prefix = b"AT\r\n\r\nOK\r\n";
self.need(Some(isok_res_prefix), None).await
};
if start_time.elapsed().as_millis() > timeout {
info!("timeout initializing modem");
}
match found_response {
FoundResponse::M1 => break,
_ => Timer::after(Duration::from_millis(1000)).await,
};
}
esp_println::println!("\nsuccessfully established communication with the modem");
Ok(())
}
/// Check that the cellular module is alive.
///
/// See if the cellular module is responding at the AT interface by poking
/// it with "AT" up to `attempts` times, waiting 1 second for an "OK"
/// response each time
pub async fn is_alive(&mut self, attempts: u8) -> Result<Status> {
for i in 0..attempts {
let (res, found_response) = {
let cmd = b"AT\r\n";
self.send_at(
Some(cmd),
Some(("is_alive: ".to_string() + &i.to_string()).as_str()),
None,
)
.await;
let isok_res_prefix = b"AT\r\n\r\nOK\r\n";
self.need(Some(isok_res_prefix), None).await
};
match found_response {
FoundResponse::M1 => {
return Ok(res.response.status);
}
FoundResponse::M2 => {
return Ok(res.response.status);
}
_ => {
continue;
}
}
}
Err(Error::Custom("Modem not responding in is_alive"))
}
pub async fn check_network_registration(&mut self) -> Result<u8> {
let response = {
let cmd = b"AT+CREG?\r\n";
self.send_at(Some(cmd), Some("check_network_registration"), None)
.await;
let res_prefix = b"AT+CREG?\r\n\r\n+CREG: ";
let res_suffix = b"\r\n\r\nOK\r\n";
self.need(Some(res_prefix), Some(res_suffix)).await
};
match response.1 {
FoundResponse::M1 => Ok(self
.modem_res
.parse_network_registaration(&response.0.response.data[..])?),
_ => Err(Error::Custom("error getting subscriber number")),
}
}
pub async fn need(
&mut self,
gps_prefixparam: Option<&[u8]>,
gps_suffixparam: Option<&[u8]>,
) -> (ModemResponse, FoundResponse) {
let gps_prefix = gps_prefixparam.unwrap_or(AT_CMD);
let gps_suffix = gps_suffixparam.unwrap_or(AT_CMD);
let mut modem_response = ModemResponse::new();
let mut found_response = FoundResponse::None;
let mut received_bytes: Vec<u8> = Vec::new();
loop {
let (_len, line_bytes) = self.signal.wait().await;
received_bytes.extend_from_slice(&line_bytes);
self.signal.reset();
info!("looping……");
if gps_prefixparam.is_some() && gps_suffixparam.is_some() {
if let Some((prefix_pos, suffix_pos)) =
find_prefix_suffix(&received_bytes, gps_prefix, gps_suffix)
{
let extracted = &received_bytes[prefix_pos..suffix_pos + gps_suffix.len()];
modem_response = self
.modem_res
.parse0(&extracted, Some(self.tosend.get_to_sent()));
found_response = FoundResponse::M1;
break;
}
} else if let Some(pos) = find_sequence(&received_bytes, gps_prefix) {
let extracted = &received_bytes[pos..pos + gps_prefix.len()];
modem_response = self
.modem_res
.parse0(&extracted, Some(self.tosend.get_to_sent()));
found_response = FoundResponse::M1;
break;
}
if find_sequence(&received_bytes, b"ERROR").is_some() {
modem_response = self
.modem_res
.parse0(&received_bytes, Some(self.tosend.get_to_sent()));
found_response = FoundResponse::M2;
break;
}
}
(modem_response, found_response)
}
}
const AT_CMD: &[u8] = b"AT\r\n";
// const AT_OK_RESPONSE: &[u8] = b"AT\r\n\r\nOK\r\n";
// const AT_CREG: &[u8] = b"AT+CREG?\r\n";
// const CREG_RESPONSE_PREFIX: &[u8] = b"AT+CREG?\r\n\r\n+CREG: ";
// const RESPONSE_SUFFIX: &[u8] = b"\r\n\r\nOK\r\n";
fn find_prefix_suffix(buffer: &[u8], prefix: &[u8], suffix: &[u8]) -> Option<(usize, usize)> {
if let Some(prefix_pos) = buffer
.windows(prefix.len())
.position(|window| window == prefix)
{
let after_prefix_pos = prefix_pos + prefix.len();
if let Some(suffix_pos) = buffer[after_prefix_pos..]
.windows(suffix.len())
.position(|window| window == suffix)
{
return Some((prefix_pos, after_prefix_pos + suffix_pos));
}
}
None
}
fn find_sequence(buffer: &[u8], sequence: &[u8]) -> Option<usize> {
buffer
.windows(sequence.len())
.position(|window| window == sequence)
}
#[derive(Debug)]
pub enum FoundResponse {
None,
M1,
M2,
}
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum RegistrationStatus {
NotRegistered = 0,
OkHome = 1,
Searching = 2,
Denied = 3,
Unknown = 4,
OkRoaming = 5,
}
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum PreferredMode {
Automatic = 2,
CDMAOnly = 9,
EVDOOnly = 10,
GSMOnly = 13,
WCDMAOnly = 14,
GSMPlusWCDMAOnly = 19,
CDMAPlusEVDOOnly = 22,
LTEOnly = 37,
GSMPlusWCDMAPlusLTEOnly = 39,
AnyButLTE = 48,
GSMPlusLTEOnly = 51,
WCDMAPlusLTEOnly = 54,
TDCDMAOnly = 59,
GSMPlusTDSCDMAOnly = 60,
GSMPlusWCDMAPlusTDSCDMAOnly = 63,
CDMAPlusEVDOPlusGSMPlusWCDMAPlusTDSCDMAOnly = 67,
}
#[derive(Debug, PartialEq)]
#[repr(u16)]
pub enum ModemErrors {
OPERATIONNOTALLOWED = 3,
EXENOTSUPPORT = 49,
EXEFAIL = 50,
PARAMINVALID = 53,
INVALIDCOMMANDLINE = 58,
OPERNOTSUPP = 303,
INVALIDTXTPARAM = 305,
INVALIDMEMINDEX = 321,
UNKNOWNERROR = 500,
}
#[derive(Debug, PartialEq)]
pub enum SIMStatus {
UNKNOWN,
READY,
SIMPIN,
SIMPUK,
PHSIMPIN,
SIMPIN2,
SIMPUK2,
PHNETPIN,
}
pub struct SignalQuality {
pub rssi: u8,
pub ber: u8,
}
impl SignalQuality {
pub fn rssi(&self) -> Option<i8> {
match self.rssi {
0 => Some(-115),
1 => Some(-111),
2..=32 => Some(-110 + 2 * (self.rssi - 2) as i8),
_ => None,
}
}
}

View file

@ -0,0 +1 @@
pub mod parse;

1012
src/run/modem/parse/parse.rs Normal file

File diff suppressed because it is too large Load diff

41
src/utils.rs Normal file
View file

@ -0,0 +1,41 @@
extern crate rand;
extern crate rand_core; // or your preferred panic handler
use rand::rngs::SmallRng;
use rand_core::RngCore;
use rand_core::SeedableRng;
#[macro_export]
macro_rules! singleton {
($val:expr, $T:ty) => {{
static STATIC_CELL: ::static_cell::StaticCell<$T> = ::static_cell::StaticCell::new();
STATIC_CELL.init($val)
}};
}
// Dummy entropy source for demonstration purposes
struct DummyEntropySource;
impl DummyEntropySource {
fn new() -> Self {
DummyEntropySource
}
fn get_entropy(&self) -> [u8; 16] {
// In a real `no_std` environment, you need to replace this with a proper entropy source.
[0x42; 16] // Just an example, replace with real entropy
}
}
pub fn generate_random_seed() -> u64 {
let entropy_source = DummyEntropySource::new();
let seed = entropy_source.get_entropy();
let mut rng = SmallRng::from_seed(seed);
// Generate random seed
let mut seed_bytes = [0u8; 8];
rng.fill_bytes(&mut seed_bytes);
u64::from_le_bytes(seed_bytes)
}