Skip to content

Commit d98b530

Browse files
authored
Merge pull request #15 from OperatorFoundation/add-features
Awesome! Thanks for all the work. Agree with holding off on +GEOMON for now. Feel free to open new issue(s) though, for that and unresponsive modem and anything else.
2 parents f3513f3 + 88fc8f9 commit d98b530

File tree

1 file changed

+223
-5
lines changed

1 file changed

+223
-5
lines changed

adafruit_rockblock.py

Lines changed: 223 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
4343
"""
4444

45+
4546
import time
4647
import 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

Comments
 (0)