Skip to content

Add features: IMEI, RSSI, Versions, Geolocation, Time, Ring Alerts, handle non-responsive modem #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 12, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 223 additions & 5 deletions adafruit_rockblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

"""


import time
import struct

Expand All @@ -60,7 +61,6 @@ def __init__(self, uart, baudrate=19200):

def _uart_xfer(self, cmd):
"""Send AT command and return response as tuple of lines read."""

self._uart.reset_input_buffer()
self._uart.write(str.encode("AT" + cmd + "\r"))

Expand All @@ -80,9 +80,13 @@ def reset(self):
self._uart_xfer("&F0") # factory defaults
self._uart_xfer("&K0") # flow control off

def _transfer_buffer(self):
"""Copy out buffer to in buffer to simulate receiving a message."""
self._uart_xfer("+SBDTC")

@property
def data_out(self):
"The binary data in the outbound buffer."
"""The binary data in the outbound buffer."""
return self._buf_out

@data_out.setter
Expand Down Expand Up @@ -199,6 +203,220 @@ def model(self):
return resp[1].strip().decode()
return None

def _transfer_buffer(self):
"""Copy out buffer to in buffer to simulate receiving a message."""
self._uart_xfer("+SBDTC")
@property
def serial_number(self):
"""Modem's serial number, also known as the modem's IMEI.

Returns
string
"""
resp = self._uart_xfer("+CGSN")
if resp[-1].strip().decode() == "OK":
return resp[1].strip().decode()
return None

@property
def signal_quality(self):
"""Signal Quality also known as the Received Signal Strength Indicator (RSSI).

Values returned are 0 to 5, where 0 is no signal (0 bars) and 5 is strong signal (5 bars).

Important note: signal strength may not be fully accurate, so waiting for
high signal strength prior to sending a message isn't always recommended.
For details see https://docs.rockblock.rock7.com/docs/checking-the-signal-strength

Returns:
int
"""
resp = self._uart_xfer("+CSQ")
if resp[-1].strip().decode() == "OK":
return int(resp[1].strip().decode().split(":")[1])
return None

@property
def revision(self):
"""Modem's internal component firmware revisions.

For example: Call Processor Version, Modem DSP Version, DBB Version (ASIC),
RFA VersionSRFA2), NVM Version, Hardware Version, BOOT Version

Returns a tuple:
(string, string, string, string, string, string, string)
"""
resp = self._uart_xfer("+CGMR")
if resp[-1].strip().decode() == "OK":
lines = []
for x in range(1, len(resp) - 2):
line = resp[x]
if line != b"\r\n":
lines.append(line.decode().strip())
return tuple(lines)
return (None,) * 7

@property
def ring_alert(self):
"""The current ring indication mode.

False means Ring Alerts are disabled, and True means Ring Alerts are enabled.

When SBD ring indication is enabled, the ISU asserts the RI line and issues
the unsolicited result code SBDRING when an SBD ring alert is received.
(Note: the network can only send ring alerts to the ISU after it has registered).

Returns:
bool
"""
resp = self._uart_xfer("+SBDMTA?")
if resp[-1].strip().decode() == "OK":
return bool(int(resp[1].strip().decode().split(":")[1]))
return None

@ring_alert.setter
def ring_alert(self, value):
if value in (True, False):
resp = self._uart_xfer("+SBDMTA=" + str(int(value)))
if resp[-1].strip().decode() == "OK":
return True
raise RuntimeError("Error setting Ring Alert.")
raise ValueError(
"Use 0 or False to disable Ring Alert or use 1 or True to enable Ring Alert."
)

@property
def ring_indication(self):
"""The ring indication status.

Returns the reason for the most recent assertion of the Ring Indicate signal.

The response contains separate indications for telephony and SBD ring indications.
The response is in the form:
(<tel_ri>,<sbd_ri>)

<tel_ri> indicates the telephony ring indication status:
0 No telephony ring alert received.
1 Incoming voice call.
2 Incoming data call.
3 Incoming fax call.

<sbd_ri> indicates the SBD ring indication status:
0 No SBD ring alert received.
1 SBD ring alert received.

Returns a tuple:
(string, string)
"""
resp = self._uart_xfer("+CRIS")
if resp[-1].strip().decode() == "OK":
return tuple(resp[1].strip().decode().split(":")[1].split(","))
return (None,) * 2

@property
def geolocation(self):
"""Most recent geolocation of the modem as measured by the Iridium constellation
including a timestamp of when geolocation measurement was made.

The response is in the form:
(<x>, <y>, <z>, <timestamp>)

<x>, <y>, <z> is a geolocation grid code from an earth centered Cartesian coordinate system,
using dimensions, x, y, and z, to specify location. The coordinate system is aligned
such that the z-axis is aligned with the north and south poles, leaving the x-axis
and y-axis to lie in the plane containing the equator. The axes are aligned such that
at 0 degrees latitude and 0 degrees longitude, both y and z are zero and
x is positive (x = +6376, representing the nominal earth radius in kilometres).
Each dimension of the geolocation grid code is displayed in decimal form using
units of kilometres. Each dimension of the geolocation grid code has a minimum value
of –6376, a maximum value of +6376, and a resolution of 4.
This geolocation coordinate system is known as ECEF (acronym earth-centered, earth-fixed),
also known as ECR (initialism for earth-centered rotational)

<timestamp> is a time_struct
The timestamp is assigned by the modem when the geolocation grid code received from
the network is stored to the modem's internal memory.

The timestamp used by the modem is Iridium system time, which is a running count of
90 millisecond intervals, since Sunday May 11, 2014, at 14:23:55 UTC (the most recent
Iridium epoch).
The timestamp returned by the modem is a 32-bit integer displayed in hexadecimal form.
We convert the modem's timestamp and return it as a time_struct.

The system time value is always expressed in UTC time.

Returns a tuple:
(int, int, int, time_struct)
"""
resp = self._uart_xfer("-MSGEO")
if resp[-1].strip().decode() == "OK":
temp = resp[1].strip().decode().split(":")[1].split(",")
ticks_since_epoch = int(temp[3], 16)
ms_since_epoch = (
ticks_since_epoch * 90
) # convert iridium ticks to milliseconds

# milliseconds to seconds
# hack to divide by 1000 and avoid using limited floating point math which throws the
# calculations off quite a bit, this should be accurate to 1 second or so
ms_str = str(ms_since_epoch)
substring = ms_str[0 : len(ms_str) - 3]
secs_since_epoch = int(substring)

# iridium epoch
iridium_epoch = time.struct_time(((2014), (5), 11, 14, 23, 55, 6, -1, -1))
iridium_epoch_unix = time.mktime(iridium_epoch)

# add timestamp's seconds to the iridium epoch
time_now_unix = iridium_epoch_unix + int(secs_since_epoch)
return (
int(temp[0]),
int(temp[1]),
int(temp[2]),
time.localtime(time_now_unix),
)
return (None,) * 4

@property
def system_time(self):
"""Current date and time as given by the Iridium network.

The system time is available and valid only after the ISU has registered with
the network and has received the Iridium system time from the network.
Once the time is received, the ISU uses its internal clock to increment the counter.
In addition, at least every 8 hours, or on location update or other event that
requires re-registration, the ISU will obtain a new system time from the network.

The timestamp used by the modem is Iridium system time, which is a running count of
90 millisecond intervals, since Sunday May 11, 2014, at 14:23:55 UTC (the most recent
Iridium epoch).
The timestamp returned by the modem is a 32-bit integer displayed in hexadecimal form.
We convert the modem's timestamp and return it as a time_struct.

The system time value is always expressed in UTC time.

Returns:
time_struct
"""
resp = self._uart_xfer("-MSSTM")
if resp[-1].strip().decode() == "OK":
temp = resp[1].strip().decode().split(":")[1]
if temp == " no network service":
return None
ticks_since_epoch = int(temp, 16)
ms_since_epoch = (
ticks_since_epoch * 90
) # convert iridium ticks to milliseconds

# milliseconds to seconds\
# hack to divide by 1000 and avoid using limited floating point math which throws the
# calculations off quite a bit, this should be accurate to 1 second or so
ms_str = str(ms_since_epoch)
substring = ms_str[0 : len(ms_str) - 3]
secs_since_epoch = int(substring)

# iridium epoch
iridium_epoch = time.struct_time(((2014), (5), 11, 14, 23, 55, 6, -1, -1))
iridium_epoch_unix = time.mktime(iridium_epoch)

# add timestamp's seconds to the iridium epoch
time_now_unix = iridium_epoch_unix + int(secs_since_epoch)
return time.localtime(time_now_unix)
return None