4242
4343"""
4444
45+
4546import time
4647import struct
4748
@@ -60,7 +61,6 @@ def __init__(self, uart, baudrate=19200):
6061
6162 def _uart_xfer (self , cmd ):
6263 """Send AT command and return response as tuple of lines read."""
63-
6464 self ._uart .reset_input_buffer ()
6565 self ._uart .write (str .encode ("AT" + cmd + "\r " ))
6666
@@ -80,9 +80,13 @@ def reset(self):
8080 self ._uart_xfer ("&F0" ) # factory defaults
8181 self ._uart_xfer ("&K0" ) # flow control off
8282
83+ def _transfer_buffer (self ):
84+ """Copy out buffer to in buffer to simulate receiving a message."""
85+ self ._uart_xfer ("+SBDTC" )
86+
8387 @property
8488 def data_out (self ):
85- "The binary data in the outbound buffer."
89+ """ The binary data in the outbound buffer."" "
8690 return self ._buf_out
8791
8892 @data_out .setter
@@ -199,6 +203,220 @@ def model(self):
199203 return resp [1 ].strip ().decode ()
200204 return None
201205
202- def _transfer_buffer (self ):
203- """Copy out buffer to in buffer to simulate receiving a message."""
204- self ._uart_xfer ("+SBDTC" )
206+ @property
207+ def serial_number (self ):
208+ """Modem's serial number, also known as the modem's IMEI.
209+
210+ Returns
211+ string
212+ """
213+ resp = self ._uart_xfer ("+CGSN" )
214+ if resp [- 1 ].strip ().decode () == "OK" :
215+ return resp [1 ].strip ().decode ()
216+ return None
217+
218+ @property
219+ def signal_quality (self ):
220+ """Signal Quality also known as the Received Signal Strength Indicator (RSSI).
221+
222+ Values returned are 0 to 5, where 0 is no signal (0 bars) and 5 is strong signal (5 bars).
223+
224+ Important note: signal strength may not be fully accurate, so waiting for
225+ high signal strength prior to sending a message isn't always recommended.
226+ For details see https://docs.rockblock.rock7.com/docs/checking-the-signal-strength
227+
228+ Returns:
229+ int
230+ """
231+ resp = self ._uart_xfer ("+CSQ" )
232+ if resp [- 1 ].strip ().decode () == "OK" :
233+ return int (resp [1 ].strip ().decode ().split (":" )[1 ])
234+ return None
235+
236+ @property
237+ def revision (self ):
238+ """Modem's internal component firmware revisions.
239+
240+ For example: Call Processor Version, Modem DSP Version, DBB Version (ASIC),
241+ RFA VersionSRFA2), NVM Version, Hardware Version, BOOT Version
242+
243+ Returns a tuple:
244+ (string, string, string, string, string, string, string)
245+ """
246+ resp = self ._uart_xfer ("+CGMR" )
247+ if resp [- 1 ].strip ().decode () == "OK" :
248+ lines = []
249+ for x in range (1 , len (resp ) - 2 ):
250+ line = resp [x ]
251+ if line != b"\r \n " :
252+ lines .append (line .decode ().strip ())
253+ return tuple (lines )
254+ return (None ,) * 7
255+
256+ @property
257+ def ring_alert (self ):
258+ """The current ring indication mode.
259+
260+ False means Ring Alerts are disabled, and True means Ring Alerts are enabled.
261+
262+ When SBD ring indication is enabled, the ISU asserts the RI line and issues
263+ the unsolicited result code SBDRING when an SBD ring alert is received.
264+ (Note: the network can only send ring alerts to the ISU after it has registered).
265+
266+ Returns:
267+ bool
268+ """
269+ resp = self ._uart_xfer ("+SBDMTA?" )
270+ if resp [- 1 ].strip ().decode () == "OK" :
271+ return bool (int (resp [1 ].strip ().decode ().split (":" )[1 ]))
272+ return None
273+
274+ @ring_alert .setter
275+ def ring_alert (self , value ):
276+ if value in (True , False ):
277+ resp = self ._uart_xfer ("+SBDMTA=" + str (int (value )))
278+ if resp [- 1 ].strip ().decode () == "OK" :
279+ return True
280+ raise RuntimeError ("Error setting Ring Alert." )
281+ raise ValueError (
282+ "Use 0 or False to disable Ring Alert or use 1 or True to enable Ring Alert."
283+ )
284+
285+ @property
286+ def ring_indication (self ):
287+ """The ring indication status.
288+
289+ Returns the reason for the most recent assertion of the Ring Indicate signal.
290+
291+ The response contains separate indications for telephony and SBD ring indications.
292+ The response is in the form:
293+ (<tel_ri>,<sbd_ri>)
294+
295+ <tel_ri> indicates the telephony ring indication status:
296+ 0 No telephony ring alert received.
297+ 1 Incoming voice call.
298+ 2 Incoming data call.
299+ 3 Incoming fax call.
300+
301+ <sbd_ri> indicates the SBD ring indication status:
302+ 0 No SBD ring alert received.
303+ 1 SBD ring alert received.
304+
305+ Returns a tuple:
306+ (string, string)
307+ """
308+ resp = self ._uart_xfer ("+CRIS" )
309+ if resp [- 1 ].strip ().decode () == "OK" :
310+ return tuple (resp [1 ].strip ().decode ().split (":" )[1 ].split ("," ))
311+ return (None ,) * 2
312+
313+ @property
314+ def geolocation (self ):
315+ """Most recent geolocation of the modem as measured by the Iridium constellation
316+ including a timestamp of when geolocation measurement was made.
317+
318+ The response is in the form:
319+ (<x>, <y>, <z>, <timestamp>)
320+
321+ <x>, <y>, <z> is a geolocation grid code from an earth centered Cartesian coordinate system,
322+ using dimensions, x, y, and z, to specify location. The coordinate system is aligned
323+ such that the z-axis is aligned with the north and south poles, leaving the x-axis
324+ and y-axis to lie in the plane containing the equator. The axes are aligned such that
325+ at 0 degrees latitude and 0 degrees longitude, both y and z are zero and
326+ x is positive (x = +6376, representing the nominal earth radius in kilometres).
327+ Each dimension of the geolocation grid code is displayed in decimal form using
328+ units of kilometres. Each dimension of the geolocation grid code has a minimum value
329+ of –6376, a maximum value of +6376, and a resolution of 4.
330+ This geolocation coordinate system is known as ECEF (acronym earth-centered, earth-fixed),
331+ also known as ECR (initialism for earth-centered rotational)
332+
333+ <timestamp> is a time_struct
334+ The timestamp is assigned by the modem when the geolocation grid code received from
335+ the network is stored to the modem's internal memory.
336+
337+ The timestamp used by the modem is Iridium system time, which is a running count of
338+ 90 millisecond intervals, since Sunday May 11, 2014, at 14:23:55 UTC (the most recent
339+ Iridium epoch).
340+ The timestamp returned by the modem is a 32-bit integer displayed in hexadecimal form.
341+ We convert the modem's timestamp and return it as a time_struct.
342+
343+ The system time value is always expressed in UTC time.
344+
345+ Returns a tuple:
346+ (int, int, int, time_struct)
347+ """
348+ resp = self ._uart_xfer ("-MSGEO" )
349+ if resp [- 1 ].strip ().decode () == "OK" :
350+ temp = resp [1 ].strip ().decode ().split (":" )[1 ].split ("," )
351+ ticks_since_epoch = int (temp [3 ], 16 )
352+ ms_since_epoch = (
353+ ticks_since_epoch * 90
354+ ) # convert iridium ticks to milliseconds
355+
356+ # milliseconds to seconds
357+ # hack to divide by 1000 and avoid using limited floating point math which throws the
358+ # calculations off quite a bit, this should be accurate to 1 second or so
359+ ms_str = str (ms_since_epoch )
360+ substring = ms_str [0 : len (ms_str ) - 3 ]
361+ secs_since_epoch = int (substring )
362+
363+ # iridium epoch
364+ iridium_epoch = time .struct_time (((2014 ), (5 ), 11 , 14 , 23 , 55 , 6 , - 1 , - 1 ))
365+ iridium_epoch_unix = time .mktime (iridium_epoch )
366+
367+ # add timestamp's seconds to the iridium epoch
368+ time_now_unix = iridium_epoch_unix + int (secs_since_epoch )
369+ return (
370+ int (temp [0 ]),
371+ int (temp [1 ]),
372+ int (temp [2 ]),
373+ time .localtime (time_now_unix ),
374+ )
375+ return (None ,) * 4
376+
377+ @property
378+ def system_time (self ):
379+ """Current date and time as given by the Iridium network.
380+
381+ The system time is available and valid only after the ISU has registered with
382+ the network and has received the Iridium system time from the network.
383+ Once the time is received, the ISU uses its internal clock to increment the counter.
384+ In addition, at least every 8 hours, or on location update or other event that
385+ requires re-registration, the ISU will obtain a new system time from the network.
386+
387+ The timestamp used by the modem is Iridium system time, which is a running count of
388+ 90 millisecond intervals, since Sunday May 11, 2014, at 14:23:55 UTC (the most recent
389+ Iridium epoch).
390+ The timestamp returned by the modem is a 32-bit integer displayed in hexadecimal form.
391+ We convert the modem's timestamp and return it as a time_struct.
392+
393+ The system time value is always expressed in UTC time.
394+
395+ Returns:
396+ time_struct
397+ """
398+ resp = self ._uart_xfer ("-MSSTM" )
399+ if resp [- 1 ].strip ().decode () == "OK" :
400+ temp = resp [1 ].strip ().decode ().split (":" )[1 ]
401+ if temp == " no network service" :
402+ return None
403+ ticks_since_epoch = int (temp , 16 )
404+ ms_since_epoch = (
405+ ticks_since_epoch * 90
406+ ) # convert iridium ticks to milliseconds
407+
408+ # milliseconds to seconds\
409+ # hack to divide by 1000 and avoid using limited floating point math which throws the
410+ # calculations off quite a bit, this should be accurate to 1 second or so
411+ ms_str = str (ms_since_epoch )
412+ substring = ms_str [0 : len (ms_str ) - 3 ]
413+ secs_since_epoch = int (substring )
414+
415+ # iridium epoch
416+ iridium_epoch = time .struct_time (((2014 ), (5 ), 11 , 14 , 23 , 55 , 6 , - 1 , - 1 ))
417+ iridium_epoch_unix = time .mktime (iridium_epoch )
418+
419+ # add timestamp's seconds to the iridium epoch
420+ time_now_unix = iridium_epoch_unix + int (secs_since_epoch )
421+ return time .localtime (time_now_unix )
422+ return None
0 commit comments