Skip to content
Merged
Changes from 1 commit
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
Next Next commit
Add functionality to return modem IMEI, RSSI (signal strength), firmw…
…are versions, geolocation, date/time per Iridium network, ability to enable/disable ring alerts, check for ring alerts
  • Loading branch information
zunkworks committed Jan 8, 2021
commit 816af16d50617831f5bdc51da12c36ac776a8c39
196 changes: 184 additions & 12 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 @@ -56,29 +57,44 @@ def __init__(self, uart, baudrate=19200):
self._uart = uart
self._uart.baudrate = baudrate
self._buf_out = None
self.reset()
# self.reset()

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

print("sending command: " + cmd)
self._uart.reset_input_buffer()
self._uart.write(str.encode("AT" + cmd + "\r"))

resp = []
line = self._uart.readline()
resp.append(line)
while not any(EOM in line for EOM in (b"OK\r\n", b"ERROR\r\n")):
line = self._uart.readline()
print("uart line: " + line.decode())
if line is None:
# print("No response from Modem")
return None
else:
resp.append(line)
while not any(EOM in line for EOM in (b"OK\r\n", b"ERROR\r\n")):
line = self._uart.readline()
print("uart line: " + line.decode())
resp.append(line)

self._uart.reset_input_buffer()
self._uart.reset_input_buffer()

return tuple(resp)
return tuple(resp)

def reset(self):
"""Perform a software reset."""
self._uart_xfer("&F0") # factory defaults
self._uart_xfer("&K0") # flow control off
if self._uart_xfer("&F0") is None: # factory defaults
return False
else:
if self._uart_xfer("&K0") is None: # flow control off
return False
else:
return True

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

@property
def data_out(self):
Expand Down Expand Up @@ -199,6 +215,162 @@ 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 imei(self):
"""Return modem imei/serial."""
resp = self._uart_xfer("+CGSN")
if resp[-1].strip().decode() == "OK":
return resp[1].strip().decode()
return None

@property
def rssi(self):
"""Return 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
"""
resp = self._uart_xfer("+CSQ")
if resp[-1].strip().decode() == "OK":
return resp[1].strip().decode().split(":")[1]
return None

@property
def version(self):
"""Return the modem components' firmware versions.
For example: Call Processor Version, Modem DSP Version, DBB Version (ASIC), RFA VersionSRFA2), NVM Version, Hardware Version, BOOT Version
"""
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 lines
return None

@property
def ring_alert(self):
"""Retrieve setting for SBD Ring Alerts."""
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=1):
"""Enable or disable ring alert feature."""
if value in [True, False]:
resp = self._uart_xfer("+SBDMTA=" + str(int(value)))
if resp[-1].strip().decode() == "OK":
return True
else:
raise RuntimeError("Error setting Ring Alert.")
else:
raise ValueError(
"Use 0 or False to disable Ring Alert or use 0 or True to enable Ring Alert."
)

@property
def ring_indication(self):
"""
Query the ring indication status, returning 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>]
where <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.
and <sbd_ri> indicates the SBD ring indication status:
0 No SBD ring alert received.
1 SBD ring alert received.
"""
resp = self._uart_xfer("+CRIS")
if resp[-1].strip().decode() == "OK":
return resp[1].strip().decode().split(":")[1].split(",")
return None

@property
def geolocation(self):
"""
Return the geolocation of the modem as measured by the Iridium constellation and the current time based on the Iridium network timestamp.
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 for earth-centered, earth-fixed), also known as ECR (initialism for earth-centered rotational)
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.
We convert the modem's timestamp and return it as a time_struct representing the current date and time UTC.
"""
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)

# convert to time struct
time_now = time.localtime(time_now_unix)

values = [
int(temp[0]),
int(temp[1]),
int(temp[2]),
time_now,
]
return values
return None

@property
def timestamp(self):
"""
Return the current date and time as given by the Iridium 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.
We convert the modem's timestamp and return it as a time_struct representing the current date and time UTC.
If the satellite network is not available then we return None
"""
resp = self._uart_xfer("-MSSTM")
if resp[-1].strip().decode() == "OK":
temp = resp[1].strip().decode().split(":")[1]
print(temp)
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)

# convert to time struct
time_now = time.localtime(time_now_unix)

return time_now
return None