use core::fmt::Write;
use heapless::{consts, String};
#[derive(Debug)]
pub enum Command<'a> {
QueryFirmwareInfo,
SetBand(LoraRegion),
SetMode(LoraMode),
GetBand,
Reset(ResetMode),
Join(ConnectMode),
SetConfig(ConfigOption<'a>),
GetConfig(ConfigKey),
Send(QoS, Port, &'a [u8]),
GetStatus,
}
#[derive(Debug, Clone, Copy)]
pub enum QoS {
Unconfirmed,
Confirmed,
}
#[derive(Debug, Clone, Copy)]
pub enum ResetMode {
Restart,
Reload,
}
#[derive(Debug, Clone, Copy)]
pub enum ConnectMode {
OTAA,
ABP,
}
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum LoraMode {
WAN = 0,
P2P = 1,
}
pub type Port = u8;
pub type DevAddr = [u8; 4];
pub type EUI = [u8; 8];
pub type AppKey = [u8; 16];
pub type NwksKey = [u8; 16];
pub type AppsKey = [u8; 16];
#[derive(Debug)]
pub enum ConfigKey {
DevAddr,
DevEui,
AppEui,
AppKey,
NwksKey,
AppsKey,
}
#[derive(Debug)]
pub enum ConfigOption<'a> {
DevAddr(&'a DevAddr),
DevEui(&'a EUI),
AppEui(&'a EUI),
AppKey(&'a AppKey),
NwksKey(&'a NwksKey),
AppsKey(&'a AppsKey),
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Response {
None,
Ok,
Error(i8),
FirmwareInfo(FirmwareInfo),
LoraBand(LoraRegion),
Recv(EventCode, Port, usize, Option<[u8; crate::RECV_BUFFER_LEN]>),
Status {
tx_ok: u8,
tx_err: u8,
rx_ok: u8,
rx_timeout: u8,
rx_err: u8,
rssi: i8,
snr: u32,
},
Initialized,
}
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum EventCode {
RecvData,
TxConfirmed,
TxUnconfirmed,
JoinedSuccess,
JoinedFailed,
TxTimeout,
Rx2Timeout,
DownlinkRepeated,
WakeUp,
P2PTxComplete,
Unknown,
}
#[derive(Debug)]
pub struct FirmwareInfo {
pub major: u8,
pub minor: u8,
pub patch: u8,
pub build: u8,
}
#[derive(Debug, Clone, Copy)]
pub enum LoraRegion {
EU868,
US915,
AU915,
KR920,
AS923,
IN865,
UNKNOWN,
}
pub type CommandBuffer = String<consts::U128>;
impl<'a> Command<'a> {
pub fn buffer() -> CommandBuffer {
String::new()
}
pub fn encode(&self, s: &mut CommandBuffer) {
match self {
Command::QueryFirmwareInfo => {
write!(s, "at+version").unwrap();
}
Command::SetBand(region) => {
write!(s, "at+band=").unwrap();
region.encode(s);
}
Command::GetBand => {
write!(s, "at+band").unwrap();
}
Command::SetMode(mode) => {
write!(s, "at+mode=").unwrap();
mode.encode(s);
}
Command::Join(mode) => {
write!(s, "at+join=").unwrap();
mode.encode(s);
}
Command::SetConfig(opt) => {
write!(s, "at+set_config=").unwrap();
opt.encode(s);
}
Command::GetConfig(key) => {
write!(s, "at+get_config=").unwrap();
key.encode(s);
}
Command::Reset(mode) => {
write!(
s,
"at+reset={}",
match mode {
ResetMode::Restart => 0,
ResetMode::Reload => 1,
}
)
.unwrap();
}
Command::Send(qos, port, data) => {
write!(
s,
"at+send={},{},{}",
match qos {
QoS::Unconfirmed => 0,
QoS::Confirmed => 1,
},
port,
HexSlice(data),
)
.unwrap();
}
Command::GetStatus => {
write!(s, "at+status").unwrap();
}
}
}
}
struct HexSlice<'a>(&'a [u8]);
impl<'a> core::fmt::Display for HexSlice<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
for b in self.0.iter() {
write!(f, "{:x}", b)?;
}
Ok(())
}
}
impl ConfigKey {
pub fn encode(&self, s: &mut CommandBuffer) {
match self {
ConfigKey::DevAddr => {
s.push_str("dev_addr").unwrap();
}
ConfigKey::DevEui => {
s.push_str("dev_eui").unwrap();
}
ConfigKey::AppEui => {
s.push_str("app_eui").unwrap();
}
ConfigKey::AppKey => {
s.push_str("app_key").unwrap();
}
ConfigKey::NwksKey => {
s.push_str("nwks_key").unwrap();
}
ConfigKey::AppsKey => {
s.push_str("apps_key").unwrap();
}
}
}
}
impl<'a> ConfigOption<'a> {
pub fn encode(&self, s: &mut CommandBuffer) {
match self {
ConfigOption::DevAddr(addr) => {
write!(
s,
"dev_addr:{:02x}{:02x}{:02x}{:02x}",
addr[0], addr[1], addr[2], addr[3]
)
.unwrap();
}
ConfigOption::DevEui(eui) => {
write!(
s,
"dev_eui:{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
eui[0], eui[1], eui[2], eui[3], eui[4], eui[5], eui[6], eui[7]
)
.unwrap();
}
ConfigOption::AppEui(eui) => {
write!(
s,
"app_eui:{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
eui[0], eui[1], eui[2], eui[3], eui[4], eui[5], eui[6], eui[7]
)
.unwrap();
}
ConfigOption::AppKey(key) => {
write!(
s,
"app_key:{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
key[0],
key[1],
key[2],
key[3],
key[4],
key[5],
key[6],
key[7],
key[8],
key[9],
key[10],
key[11],
key[12],
key[13],
key[14],
key[15]
)
.unwrap();
}
ConfigOption::NwksKey(key) => {
write!(
s,
"nwks_key:{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
key[0],
key[1],
key[2],
key[3],
key[4],
key[5],
key[6],
key[7],
key[8],
key[9],
key[10],
key[11],
key[12],
key[13],
key[14],
key[15]
)
.unwrap();
}
ConfigOption::AppsKey(key) => {
write!(
s,
"apps_key:{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
key[0],
key[1],
key[2],
key[3],
key[4],
key[5],
key[6],
key[7],
key[8],
key[9],
key[10],
key[11],
key[12],
key[13],
key[14],
key[15]
)
.unwrap();
}
}
}
}
impl ConnectMode {
pub fn encode(&self, s: &mut CommandBuffer) {
let val = match self {
ConnectMode::OTAA => "otaa",
ConnectMode::ABP => "abp",
};
s.push_str(val).unwrap();
}
pub fn parse(d: &[u8]) -> ConnectMode {
if let Ok(s) = core::str::from_utf8(d) {
match s {
"abp" => ConnectMode::ABP,
_ => ConnectMode::OTAA,
}
} else {
ConnectMode::OTAA
}
}
}
impl LoraMode {
pub fn encode(&self, s: &mut CommandBuffer) {
let val = match self {
LoraMode::WAN => "0",
LoraMode::P2P => "1",
};
s.push_str(val).unwrap();
}
pub fn parse(d: &[u8]) -> LoraMode {
if let Ok(s) = core::str::from_utf8(d) {
match s {
"1" => LoraMode::P2P,
_ => LoraMode::WAN,
}
} else {
LoraMode::WAN
}
}
}
impl LoraRegion {
pub fn encode(&self, s: &mut CommandBuffer) {
let val = match self {
LoraRegion::EU868 => "EU868",
LoraRegion::US915 => "US915",
LoraRegion::AU915 => "AU915",
LoraRegion::KR920 => "KR920",
LoraRegion::AS923 => "AS923",
LoraRegion::IN865 => "IN865",
LoraRegion::UNKNOWN => "UNKNOWN",
};
s.push_str(val).unwrap();
}
pub fn parse(d: &[u8]) -> LoraRegion {
if let Ok(s) = core::str::from_utf8(d) {
match s {
"EU868" => LoraRegion::EU868,
"US915" => LoraRegion::US915,
"AU915" => LoraRegion::AU915,
"KR920" => LoraRegion::KR920,
"AS923" => LoraRegion::AS923,
"IN865" => LoraRegion::IN865,
_ => LoraRegion::UNKNOWN,
}
} else {
LoraRegion::UNKNOWN
}
}
}
impl EventCode {
pub fn parse(d: u8) -> EventCode {
match d {
0 => EventCode::RecvData,
1 => EventCode::TxConfirmed,
2 => EventCode::TxUnconfirmed,
3 => EventCode::JoinedSuccess,
4 => EventCode::JoinedFailed,
5 => EventCode::TxTimeout,
6 => EventCode::Rx2Timeout,
7 => EventCode::DownlinkRepeated,
8 => EventCode::WakeUp,
9 => EventCode::P2PTxComplete,
_ => EventCode::Unknown,
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}