sms_client/types.rs
1//! Generic types that apply to both HTTP and Websocket interfaces.
2
3use serde::{Serialize, Deserialize};
4
5/// Represents a stored SMS message from the database.
6#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
7pub struct SmsStoredMessage {
8
9 /// Unique identifier for the message.
10 pub message_id: i64,
11
12 /// The phone number associated with this message.
13 pub phone_number: String,
14
15 /// The actual text content of the message.
16 pub message_content: String,
17
18 /// Optional reference number for message tracking.
19 /// This is assigned by the modem and is only present for outgoing messages.
20 pub message_reference: Option<u8>,
21
22 /// Whether this message was sent (true) or received (false).
23 pub is_outgoing: bool,
24
25 /// Current status of the message (e.g., "sent", "delivered", "failed").
26 pub status: String,
27
28 /// Unix timestamp when the message was created.
29 pub created_at: Option<u32>,
30
31 /// Optional Unix timestamp when the message was completed/delivered.
32 pub completed_at: Option<u32>
33}
34
35/// A partial message delivery report, as it comes from the modem.
36#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
37pub struct SmsPartialDeliveryReport {
38
39 /// The target phone number that received the message (and has now sent back a delivery report).
40 phone_number: String,
41
42 /// The modem assigned message reference, this is basically useless outside short-term tracking
43 /// the message_id is unique should always be used instead for identification.
44 reference_id: u8,
45
46 /// The SMS TP-Status: <https://www.etsi.org/deliver/etsi_ts/123000_123099/123040/16.00.00_60/ts_123040v160000p.pdf#page=71>
47 status: u8
48}
49
50/// <https://www.etsi.org/deliver/etsi_ts/123000_123099/123040/16.00.00_60/ts_123040v160000p.pdf#page=71>
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[repr(u8)]
53pub enum SmsDeliveryReportStatus {
54 // Short message transaction completed (0x00-0x1F)
55 /// Short message received by the SME successfully
56 ReceivedBySme = 0x00,
57 /// Short message forwarded by the SC to the SME but delivery confirmation unavailable
58 ForwardedButUnconfirmed = 0x01,
59 /// Short message replaced by the SC
60 ReplacedBySc = 0x02,
61 // 0x03-0x0F Reserved
62 // 0x10-0x1F SC specific values
63
64 // Temporary error, SC still trying (0x20-0x3F)
65 /// Network congestion preventing delivery, SC will retry
66 Congestion = 0x20,
67 /// SME is busy, SC will retry delivery
68 SmeBusy = 0x21,
69 /// No response from SME, SC will retry delivery
70 NoResponseFromSme = 0x22,
71 /// Service rejected by network, SC will retry delivery
72 ServiceRejected = 0x23,
73 /// Quality of service not available, SC will retry delivery
74 QualityOfServiceNotAvailable = 0x24,
75 /// Error in SME, SC will retry delivery
76 ErrorInSme = 0x25,
77 // 0x26-0x2F Reserved
78 // 0x30-0x3F SC specific values
79
80 // Permanent error, SC not making more attempts (0x40-0x5F)
81 /// Remote procedure error - permanent failure
82 RemoteProcedureError = 0x40,
83 /// Incompatible destination - permanent failure
84 IncompatibleDestination = 0x41,
85 /// Connection rejected by SME - permanent failure
86 ConnectionRejectedBySme = 0x42,
87 /// Destination not obtainable - permanent failure
88 NotObtainable = 0x43,
89 /// Quality of service not available - permanent failure
90 QualityOfServiceNotAvailablePermanent = 0x44,
91 /// No interworking available - permanent failure
92 NoInterworkingAvailable = 0x45,
93 /// SM validity period expired - permanent failure
94 SmValidityPeriodExpired = 0x46,
95 /// SM deleted by originating SME - permanent failure
96 SmDeletedByOriginatingSme = 0x47,
97 /// SM deleted by SC administration - permanent failure
98 SmDeletedByScAdministration = 0x48,
99 /// SM does not exist in SC - permanent failure
100 SmDoesNotExist = 0x49,
101 // 0x4A-0x4F Reserved
102 // 0x50-0x5F SC specific values
103
104 // Temporary error, SC not making more attempts (0x60-0x7F)
105 /// Network congestion, SC has stopped retry attempts
106 CongestionNoRetry = 0x60,
107 /// SME busy, SC has stopped retry attempts
108 SmeBusyNoRetry = 0x61,
109 /// No response from SME, SC has stopped retry attempts
110 NoResponseFromSmeNoRetry = 0x62,
111 /// Service rejected, SC has stopped retry attempts
112 ServiceRejectedNoRetry = 0x63,
113 /// Quality of service not available, SC has stopped retry attempts
114 QualityOfServiceNotAvailableNoRetry = 0x64,
115 /// Error in SME, SC has stopped retry attempts
116 ErrorInSmeNoRetry = 0x65,
117 // 0x66-0x69 Reserved
118 // 0x6A-0x6F Reserved
119 // 0x70-0x7F SC specific values
120
121 /// Unknown or reserved status code - treated as service rejected per spec
122 Unknown(u8),
123}
124impl From<u8> for SmsDeliveryReportStatus {
125 fn from(value: u8) -> Self {
126 use SmsDeliveryReportStatus::*;
127
128 match value {
129 // Transaction completed successfully
130 0x00 => ReceivedBySme,
131 0x01 => ForwardedButUnconfirmed,
132 0x02 => ReplacedBySc,
133
134 // Temporary errors, SC still trying
135 0x20 => Congestion,
136 0x21 => SmeBusy,
137 0x22 => NoResponseFromSme,
138 0x23 => ServiceRejected,
139 0x24 => QualityOfServiceNotAvailable,
140 0x25 => ErrorInSme,
141
142 // Permanent errors
143 0x40 => RemoteProcedureError,
144 0x41 => IncompatibleDestination,
145 0x42 => ConnectionRejectedBySme,
146 0x43 => NotObtainable,
147 0x44 => QualityOfServiceNotAvailablePermanent,
148 0x45 => NoInterworkingAvailable,
149 0x46 => SmValidityPeriodExpired,
150 0x47 => SmDeletedByOriginatingSme,
151 0x48 => SmDeletedByScAdministration,
152 0x49 => SmDoesNotExist,
153
154 // Temporary errors, SC not retrying
155 0x60 => CongestionNoRetry,
156 0x61 => SmeBusyNoRetry,
157 0x62 => NoResponseFromSmeNoRetry,
158 0x63 => ServiceRejectedNoRetry,
159 0x64 => QualityOfServiceNotAvailableNoRetry,
160 0x65 => ErrorInSmeNoRetry,
161
162 // All other values (reserved, SC-specific, or unknown)
163 _ => Unknown(value)
164 }
165 }
166}
167impl SmsDeliveryReportStatus {
168 /// Returns true if the SMS was successfully delivered to the SME
169 pub fn is_successful(&self) -> bool {
170 matches!(self,
171 Self::ReceivedBySme |
172 Self::ForwardedButUnconfirmed |
173 Self::ReplacedBySc
174 )
175 }
176
177 /// Returns true if this is a temporary error where SC is still trying
178 pub fn is_temporary_retrying(&self) -> bool {
179 use SmsDeliveryReportStatus::*;
180
181 matches!(self,
182 Congestion | SmeBusy | NoResponseFromSme | ServiceRejected |
183 QualityOfServiceNotAvailable | ErrorInSme
184 ) || matches!(self, Unknown(val) if *val >= 0x20 && *val <= 0x3F)
185 }
186
187 /// Returns true if this is a permanent error (no more delivery attempts)
188 pub fn is_permanent_error(&self) -> bool {
189 use SmsDeliveryReportStatus::*;
190
191 matches!(self,
192 RemoteProcedureError | IncompatibleDestination | ConnectionRejectedBySme |
193 NotObtainable | QualityOfServiceNotAvailablePermanent | NoInterworkingAvailable |
194 SmValidityPeriodExpired | SmDeletedByOriginatingSme | SmDeletedByScAdministration |
195 SmDoesNotExist
196 ) || matches!(self, Unknown(val) if *val >= 0x40 && *val <= 0x5F)
197 }
198
199 /// Returns true if this is a temporary error where SC has stopped trying
200 pub fn is_temporary_no_retry(&self) -> bool {
201 use SmsDeliveryReportStatus::*;
202
203 matches!(self,
204 CongestionNoRetry | SmeBusyNoRetry | NoResponseFromSmeNoRetry |
205 ServiceRejectedNoRetry | QualityOfServiceNotAvailableNoRetry | ErrorInSmeNoRetry
206 ) || matches!(self, Unknown(val) if *val >= 0x60 && *val <= 0x7F)
207 }
208
209 /// Converts the status to a simplified status group for easier categorization
210 pub fn to_status_group(&self) -> SmsDeliveryReportStatusGroup {
211 if self.is_successful() {
212 SmsDeliveryReportStatusGroup::Received
213 } else if self.is_temporary_retrying() {
214 SmsDeliveryReportStatusGroup::Sent
215 } else if self.is_permanent_error() || self.is_temporary_no_retry() {
216 // Both permanent errors and temporary errors with no retry are treated as failures
217 if self.is_permanent_error() {
218 SmsDeliveryReportStatusGroup::PermanentFailure
219 } else {
220 SmsDeliveryReportStatusGroup::TemporaryFailure
221 }
222 } else {
223
224 // For unknown status codes, classify based on their range.
225 match self {
226 Self::Unknown(val) if *val >= 0x20 && *val <= 0x3F => SmsDeliveryReportStatusGroup::Sent,
227 Self::Unknown(val) if *val >= 0x40 && *val <= 0x5F => SmsDeliveryReportStatusGroup::PermanentFailure,
228 Self::Unknown(val) if *val >= 0x60 && *val <= 0x7F => SmsDeliveryReportStatusGroup::TemporaryFailure,
229 _ => SmsDeliveryReportStatusGroup::PermanentFailure // Default for truly unknown codes
230 }
231 }
232 }
233}
234
235/// Generalised group for message delivery status.
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum SmsDeliveryReportStatusGroup {
238
239 /// Message was sent but delivery is still pending (temporary errors with retry)
240 Sent,
241 /// Message was successfully received by the destination.
242 Received,
243 /// Temporary delivery failure where SC has stopped retrying.
244 TemporaryFailure,
245 /// Permanent delivery failure - message will not be delivered.
246 PermanentFailure
247}
248impl From<SmsDeliveryReportStatus> for SmsDeliveryReportStatusGroup {
249 fn from(status: SmsDeliveryReportStatus) -> Self {
250 status.to_status_group()
251 }
252}
253
254/// Represents the current status of the modem.
255#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
256pub enum ModemStatusUpdateState {
257
258 /// Modem is starting up.
259 Startup,
260
261 /// Modem is online and operational.
262 Online,
263
264 /// Modem is shutting down.
265 ShuttingDown,
266
267 /// Modem is offline and not operational.
268 Offline
269}
270impl std::fmt::Display for ModemStatusUpdateState {
271 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
272 match self {
273 ModemStatusUpdateState::Startup => write!(f, "Startup"),
274 ModemStatusUpdateState::Online => write!(f, "Online"),
275 ModemStatusUpdateState::ShuttingDown => write!(f, "ShuttingDown"),
276 ModemStatusUpdateState::Offline => write!(f, "Offline")
277 }
278 }
279}
280
281/// GNSS (Global Navigation Satellite System) fix status.
282#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
283pub enum GnssFixStatus {
284
285 /// GNSS fix status is unknown.
286 Unknown,
287
288 /// No GNSS fix.
289 NotFix,
290
291 /// 2D GNSS fix (latitude and longitude only).
292 Fix2D,
293
294 /// 3D GNSS fix (latitude, longitude, and altitude).
295 Fix3D
296}
297
298/// Represents a GNSS position report with optional fields for satellite info.
299#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
300pub struct GnssPositionReport {
301
302 /// Indicates whether the GNSS receiver is currently running.
303 pub run_status: bool,
304
305 /// Whether a valid fix has been obtained.
306 pub fix_status: bool,
307
308 /// UTC time of the position report in ISO 8601 format.
309 pub utc_time: String,
310
311 /// Latitude in decimal degrees.
312 pub latitude: Option<f64>,
313
314 /// Longitude in decimal degrees.
315 pub longitude: Option<f64>,
316
317 /// Mean sea level altitude in meters.
318 pub msl_altitude: Option<f64>,
319
320 /// Ground speed in meters per second.
321 pub ground_speed: Option<f32>,
322
323 /// Ground course in degrees.
324 pub ground_course: Option<f32>,
325
326 /// Fix mode indicating 2D/3D fix or unknown.
327 pub fix_mode: GnssFixStatus,
328
329 /// Horizontal Dilution of Precision.
330 pub hdop: Option<f32>,
331
332 /// Position Dilution of Precision.
333 pub pdop: Option<f32>,
334
335 /// Vertical Dilution of Precision.
336 pub vdop: Option<f32>,
337
338 /// Number of GPS satellites in view.
339 pub gps_in_view: Option<u8>,
340
341 /// Number of GNSS satellites used in the fix.
342 pub gnss_used: Option<u8>,
343
344 /// Number of GLONASS satellites in view.
345 pub glonass_in_view: Option<u8>
346}