From 816af16d50617831f5bdc51da12c36ac776a8c39 Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 20:48:41 -0600 Subject: [PATCH 01/14] Add functionality to return modem IMEI, RSSI (signal strength), firmware versions, geolocation, date/time per Iridium network, ability to enable/disable ring alerts, check for ring alerts --- adafruit_rockblock.py | 196 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 184 insertions(+), 12 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index fe65777..0d8c8ff 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -42,6 +42,7 @@ """ + import time import struct @@ -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): @@ -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: + [,] + where 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 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: + [,,,] + ,, 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 From b566cd181d10ef38c5b20d4ed1f159b52bc29c4b Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 21:06:29 -0600 Subject: [PATCH 02/14] remove debug print statements --- adafruit_rockblock.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 0d8c8ff..0ddab07 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -61,13 +61,11 @@ def __init__(self, uart, baudrate=19200): 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() - print("uart line: " + line.decode()) if line is None: # print("No response from Modem") return None @@ -75,7 +73,6 @@ def _uart_xfer(self, cmd): 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() From bd803a6c750bfe076ceed6913f134a06ec884b9d Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 21:22:00 -0600 Subject: [PATCH 03/14] return the reset to init --- adafruit_rockblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 0ddab07..b18d76d 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -57,7 +57,7 @@ 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.""" From 36bd875842118403f0bda7d74aeda051eac7aa52 Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 21:39:08 -0600 Subject: [PATCH 04/14] edit comments to fix pylint "line too long" errors --- adafruit_rockblock.py | 54 ++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index b18d76d..0514b2b 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -222,8 +222,10 @@ def imei(self): @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. + """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") @@ -234,7 +236,8 @@ def rssi(self): @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 + 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": @@ -271,7 +274,9 @@ def ring_alert(self, value=1): @property def ring_indication(self): """ - Query the ring indication status, returning the reason for the most recent assertion of the Ring Indicate signal. + 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: [,] @@ -292,14 +297,27 @@ def ring_indication(self): @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. + 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: [,,,] - ,, 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. + ,, 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) + + 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. """ resp = self._uart_xfer("-MSGEO") if resp[-1].strip().decode() == "OK": @@ -310,7 +328,8 @@ def geolocation(self): ) # 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 + # 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) @@ -338,9 +357,11 @@ def geolocation(self): 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 + 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. """ resp = self._uart_xfer("-MSSTM") if resp[-1].strip().decode() == "OK": @@ -353,8 +374,9 @@ def timestamp(self): 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 + # 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) From 2347133c5596fa3348e850513812b7c250ca9890 Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 21:47:15 -0600 Subject: [PATCH 05/14] fix line length problems, remove unneeded else statements --- adafruit_rockblock.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 0514b2b..b60f721 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -67,27 +67,24 @@ def _uart_xfer(self, cmd): resp = [] line = self._uart.readline() if line is None: - # print("No response from Modem") + # 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() resp.append(line) - while not any(EOM in line for EOM in (b"OK\r\n", b"ERROR\r\n")): - line = self._uart.readline() - 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.""" 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 + if self._uart_xfer("&K0") is None: # flow control off + return False + return True def _transfer_buffer(self): """Copy out buffer to in buffer to simulate receiving a message.""" @@ -264,8 +261,7 @@ def ring_alert(self, value=1): resp = self._uart_xfer("+SBDMTA=" + str(int(value))) if resp[-1].strip().decode() == "OK": return True - else: - raise RuntimeError("Error setting Ring Alert.") + 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." @@ -302,14 +298,14 @@ def geolocation(self): The response is in the form: [,,,] ,, 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. + 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) From bf3e31dd8e361b578d351ac56f4b56d23dcd73fa Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 21:50:06 -0600 Subject: [PATCH 06/14] remove last unneeded else --- adafruit_rockblock.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index b60f721..fc4da20 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -262,10 +262,9 @@ def ring_alert(self, value=1): if resp[-1].strip().decode() == "OK": return True 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." - ) + raise ValueError( + "Use 0 or False to disable Ring Alert or use 0 or True to enable Ring Alert." + ) @property def ring_indication(self): From 3f290df591d1475a6f49ecf6b41308230e39779c Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 21:57:23 -0600 Subject: [PATCH 07/14] remove indentation from comments to pass build docs check --- adafruit_rockblock.py | 51 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index fc4da20..9e3ffc1 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -222,7 +222,7 @@ 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. + 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") @@ -275,14 +275,16 @@ def ring_indication(self): The response contains separate indications for telephony and SBD ring indications. The response is in the form: [,] - where 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 indicates the SBD ring indication status: - 0 No SBD ring alert received. - 1 SBD ring alert received. + + indicates the telephony ring indication status: + 0 No telephony ring alert received. + 1 Incoming voice call. + 2 Incoming data call. + 3 Incoming fax call. + + 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": @@ -296,22 +298,25 @@ def geolocation(self): and the current time based on the Iridium network timestamp. The response is in the form: [,,,] + ,, 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. + 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) + also known as ECR (initialism for earth-centered rotational) + 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 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. + 90 millisecond intervals, since Sunday May 11, 2014, at 14:23:55 UTC. + 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. """ resp = self._uart_xfer("-MSGEO") @@ -353,9 +358,9 @@ def timestamp(self): """ Return the current date and time as given by the Iridium network 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 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. + 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. """ resp = self._uart_xfer("-MSSTM") @@ -371,7 +376,7 @@ def timestamp(self): # 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 + # 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) From 1f63d6294062ebb3c660190142b2c5547f340bdd Mon Sep 17 00:00:00 2001 From: J Date: Thu, 7 Jan 2021 21:59:55 -0600 Subject: [PATCH 08/14] remove trailing whitespace --- adafruit_rockblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 9e3ffc1..820621d 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -281,7 +281,7 @@ def ring_indication(self): 1 Incoming voice call. 2 Incoming data call. 3 Incoming fax call. - + indicates the SBD ring indication status: 0 No SBD ring alert received. 1 SBD ring alert received. From 4f04d3aa989b42b55e326c050cdc3efb96c60d6e Mon Sep 17 00:00:00 2001 From: J Date: Mon, 11 Jan 2021 16:34:08 -0600 Subject: [PATCH 09/14] remove return None from _uart_xfer, None is appended to resp. --- adafruit_rockblock.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 820621d..0ed8cac 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -66,9 +66,6 @@ def _uart_xfer(self, cmd): resp = [] line = self._uart.readline() - if line is None: - # No response from Modem - return None resp.append(line) while not any(EOM in line for EOM in (b"OK\r\n", b"ERROR\r\n")): line = self._uart.readline() From 8e0edb6f3bff583c4482788d2caf93c15840d4f8 Mon Sep 17 00:00:00 2001 From: J Date: Mon, 11 Jan 2021 16:35:02 -0600 Subject: [PATCH 10/14] rename imei to serial_number, eaiser to understand more closely reflects manufacturer's documentation --- adafruit_rockblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 0ed8cac..9b78b58 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -207,7 +207,7 @@ def model(self): return None @property - def imei(self): + def serial_number(self): """Return modem imei/serial.""" resp = self._uart_xfer("+CGSN") if resp[-1].strip().decode() == "OK": From a51d021c6a59e94066b08d37cb8f7b259bc2473b Mon Sep 17 00:00:00 2001 From: J Date: Mon, 11 Jan 2021 16:36:23 -0600 Subject: [PATCH 11/14] rename version to revision to more closely match manufacturer's documentation --- adafruit_rockblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 9b78b58..e74ea6a 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -228,7 +228,7 @@ def rssi(self): return None @property - def version(self): + def revision(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 From f8cbc85c59ae76955a4748af338a5f0d5c198089 Mon Sep 17 00:00:00 2001 From: J Date: Mon, 11 Jan 2021 18:09:16 -0600 Subject: [PATCH 12/14] make revisions to docstrings, property names, return types, typos --- adafruit_rockblock.py | 115 +++++++++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 36 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index e74ea6a..78f3bac 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -77,9 +77,9 @@ def _uart_xfer(self, cmd): def reset(self): """Perform a software reset.""" - if self._uart_xfer("&F0") is None: # factory defaults + if self._uart_xfer("&F0")[0] is None: # factory defaults return False - if self._uart_xfer("&K0") is None: # flow control off + if self._uart_xfer("&K0")[0] is None: # flow control off return False return True @@ -89,7 +89,7 @@ def _transfer_buffer(self): @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 @@ -208,30 +208,43 @@ def model(self): @property def serial_number(self): - """Return modem imei/serial.""" + """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 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. + 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 resp[1].strip().decode().split(":")[1] + return int(resp[1].strip().decode().split(":")[1]) return None @property def revision(self): - """Return the modem components' firmware versions. + """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": @@ -240,12 +253,22 @@ def revision(self): line = resp[x] if line != b"\r\n": lines.append(line.decode().strip()) - return lines - return None + return tuple(lines) + return (None,) * 7 @property def ring_alert(self): - """Retrieve setting for SBD Ring Alerts.""" + """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])) @@ -253,25 +276,24 @@ def ring_alert(self): @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 raise RuntimeError("Error setting Ring Alert.") raise ValueError( - "Use 0 or False to disable Ring Alert or use 0 or True to enable Ring Alert." + "Use 0 or False to disable Ring Alert or use 1 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 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: - [,] + (,) indicates the telephony ring indication status: 0 No telephony ring alert received. @@ -282,21 +304,24 @@ def ring_indication(self): 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 resp[1].strip().decode().split(":")[1].split(",") - return None + return tuple(resp[1].strip().decode().split(":")[1].split(",")) + return (None,) * 2 @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. + """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: - [,,,] + (, , , ) - ,, is a geolocation grid code from an earth centered Cartesian coordinate system, + , , 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 @@ -311,10 +336,17 @@ def geolocation(self): 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. + 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": @@ -347,18 +379,29 @@ def geolocation(self): int(temp[2]), time_now, ] - return values - return None + return tuple(values) + return (None,) * 4 @property - def timestamp(self): - """ - Return the current date and time as given by the Iridium network - The timestamp is assigned by the modem when the geolocation grid code received from - the network is stored to the modem's internal memory. + 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. + 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": From f4600502f60e4b8daf1ca7e31665ea759d6bff4f Mon Sep 17 00:00:00 2001 From: J Date: Mon, 11 Jan 2021 18:15:37 -0600 Subject: [PATCH 13/14] remove debug print statement --- adafruit_rockblock.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 78f3bac..8021e98 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -406,7 +406,6 @@ def system_time(self): 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) From 88fc8f9d66d4f163847803c64446eaefefb44d19 Mon Sep 17 00:00:00 2001 From: J Date: Tue, 12 Jan 2021 11:01:55 -0600 Subject: [PATCH 14/14] revert reset function, changed function to use tuple instead of list, reduced lines of code and memory usage in geolocation and system_time, removed default value in the ring alert setter --- adafruit_rockblock.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/adafruit_rockblock.py b/adafruit_rockblock.py index 8021e98..018aeda 100644 --- a/adafruit_rockblock.py +++ b/adafruit_rockblock.py @@ -77,11 +77,8 @@ def _uart_xfer(self, cmd): def reset(self): """Perform a software reset.""" - if self._uart_xfer("&F0")[0] is None: # factory defaults - return False - if self._uart_xfer("&K0")[0] is None: # flow control off - return False - return True + 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.""" @@ -275,8 +272,8 @@ def ring_alert(self): return None @ring_alert.setter - def ring_alert(self, value=1): - if value in [True, False]: + 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 @@ -369,17 +366,12 @@ def geolocation(self): # 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 = [ + return ( int(temp[0]), int(temp[1]), int(temp[2]), - time_now, - ] - return tuple(values) + time.localtime(time_now_unix), + ) return (None,) * 4 @property @@ -426,9 +418,5 @@ def system_time(self): # 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 time.localtime(time_now_unix) return None