first commit
This commit is contained in:
commit
fa60f22822
25 changed files with 2835 additions and 0 deletions
14
.cargo/config.toml
Normal file
14
.cargo/config.toml
Normal 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
12
.gitignore
vendored
Normal 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
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.check.allTargets": false,
|
||||
}
|
BIN
A9G GPRS Operation.png
Normal file
BIN
A9G GPRS Operation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 238 KiB |
108
Cargo.toml
Normal file
108
Cargo.toml
Normal 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
201
LICENSE-APACHE
Normal 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
25
LICENSE-MIT
Normal 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
0
README.md
Normal file
3
build.rs
Normal file
3
build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("cargo:rustc-link-arg-bins=-Tlinkall.x");
|
||||
}
|
6
partitions.csv
Normal file
6
partitions.csv
Normal 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,
|
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "esp"
|
122
src/gps.rs
Normal file
122
src/gps.rs
Normal 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
85
src/lib.rs
Normal 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
317
src/main.rs
Normal 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
1
src/run/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod modem;
|
82
src/run/modem/apn.rs
Normal file
82
src/run/modem/apn.rs
Normal 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)
|
||||
}
|
||||
}
|
435
src/run/modem/coms/gprs/mod.rs
Normal file
435
src/run/modem/coms/gprs/mod.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
1
src/run/modem/coms/mod.rs
Normal file
1
src/run/modem/coms/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod gprs;
|
14
src/run/modem/errors.rs
Normal file
14
src/run/modem/errors.rs
Normal 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
44
src/run/modem/impls.rs
Normal 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
6
src/run/modem/mod.rs
Normal 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
300
src/run/modem/module.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
1
src/run/modem/parse/mod.rs
Normal file
1
src/run/modem/parse/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod parse;
|
1012
src/run/modem/parse/parse.rs
Normal file
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
41
src/utils.rs
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue