From bf083160d37fdd2c461488ca81f3c5303b1b25c9 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 15:27:26 -0600 Subject: [PATCH 01/26] raise an authentication error if its unable to get keys --- Hologram/HologramCloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Hologram/HologramCloud.py b/Hologram/HologramCloud.py index 6c730363..6489e960 100755 --- a/Hologram/HologramCloud.py +++ b/Hologram/HologramCloud.py @@ -14,7 +14,7 @@ from Hologram.CustomCloud import CustomCloud from HologramAuth import TOTPAuthentication, SIMOTPAuthentication from Hologram.Authentication import CSRPSKAuthentication -from Exceptions.HologramError import HologramError +from Exceptions.HologramError import HologramError, AuthenticationError DEFAULT_SEND_MESSAGE_TIMEOUT = 5 HOLOGRAM_HOST_SEND = 'cloudsocket.hologram.io' @@ -125,6 +125,7 @@ def __populate_totp_credentials(self): self.authentication.credentials['private_key'] = self.network.imsi except Exception as e: self.logger.error('Unable to fetch device id or private key') + raise AuthenticationError('Unable to fetch device id or private key for TOTP authenication') def __populate_sim_otp_credentials(self): nonce = self.request_hex_nonce() From d73ca84216f96ba8bbc0463089c565b3dbe5ab68 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 16:17:11 -0600 Subject: [PATCH 02/26] test for new error and add new test file --- tests/API/test_API.py | 83 +++++++++++++++++++++++++ tests/MessageMode/test_HologramCloud.py | 9 ++- 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/API/test_API.py diff --git a/tests/API/test_API.py b/tests/API/test_API.py new file mode 100644 index 00000000..e3a7641f --- /dev/null +++ b/tests/API/test_API.py @@ -0,0 +1,83 @@ +import sys +import pytest +from unittest.mock import Mock, patch +sys.path.append(".") +sys.path.append("..") +sys.path.append("../..") +from Hologram.Api import Api +from Exceptions.HologramError import ApiError + + +credentials = {'devicekey':'12345678'} + +class TestHologramAPI: + + def test_create_no_creds(self): + with pytest.raises(ApiError, match = 'Must specify valid HTTP authentication credentials'): + Api() + + def test_create_missing_password(self): + with pytest.raises(ApiError, match = 'Must specify valid HTTP authentication credentials'): + Api(username='user') + + def test_create_missing_username(self): + with pytest.raises(ApiError, match = 'Must specify valid HTTP authentication credentials'): + Api(password='password') + + @patch('requests.post') + def test_activate(self, r_post): + api = Api(apikey='123apikey') + + r_post.return_value.json = Mock(return_value={"success": True, 'order_data': {}}) + + success, response = api.activateSIM('iccid') + + assert success == True + assert response == {} + + @patch('requests.post') + def test_activate_bad_status_code(self, r_post): + api = Api(apikey='123apikey') + + r_post.return_value = Mock( + status_code=429, + text = 'Too many requests') + + success, response = api.activateSIM('iccid') + + assert success == False + assert response == 'Too many requests' + + @patch('requests.post') + def test_activate_failed(self, r_post): + api = Api(apikey='123apikey') + + r_post.return_value.json = Mock(return_value={"success": False, 'data': {'iccid': 'Activation failed'}}) + + success, response = api.activateSIM('iccid') + + assert success == False + assert response == 'Activation failed' + + @patch('requests.get') + def test_get_plans(self, r_post): + api = Api(apikey='123apikey') + + r_post.return_value.json = Mock(return_value={"success": True, 'data': {'id': 1, 'orgid': 1}}) + + success, response = api.getPlans() + + assert success == True + assert response == {'id': 1, 'orgid': 1} + + @patch('requests.get') + def test_get_sim_state(self, r_post): + api = Api(apikey='123apikey') + + r_post.return_value.json = Mock(return_value={"success": True, 'data': [{'state': 'LIVE'}]}) + + success, response = api.getSIMState('iccid') + + assert success == True + assert response == {'id': 1, 'orgid': 1} + diff --git a/tests/MessageMode/test_HologramCloud.py b/tests/MessageMode/test_HologramCloud.py index e2d328d8..89345c05 100644 --- a/tests/MessageMode/test_HologramCloud.py +++ b/tests/MessageMode/test_HologramCloud.py @@ -13,19 +13,24 @@ sys.path.append("../..") from Hologram.Authentication import * from Hologram.HologramCloud import HologramCloud +from Exceptions.HologramError import AuthenticationError credentials = {'devicekey':'12345678'} class TestHologramCloud: def test_create(self): - hologram = HologramCloud(credentials, enable_inbound = False) + hologram = HologramCloud(credentials, authentication_type='csrpsk', enable_inbound = False) assert hologram.send_host == 'cloudsocket.hologram.io' assert hologram.send_port == 9999 assert hologram.receive_host == '0.0.0.0' assert hologram.receive_port == 4010 + def test_create_bad_totp_keys(self): + with pytest.raises(AuthenticationError, match = 'Unable to fetch device id or private key for TOTP authenication'): + HologramCloud(credentials, enable_inbound = False) + def test_invalid_sms_length(self): hologram = HologramCloud(credentials, authentication_type='csrpsk', enable_inbound = False) @@ -36,7 +41,7 @@ def test_invalid_sms_length(self): def test_get_result_string(self): - hologram = HologramCloud(credentials, enable_inbound = False) + hologram = HologramCloud(credentials, authentication_type='csrpsk', enable_inbound = False) assert hologram.getResultString(-1) == 'Unknown error' assert hologram.getResultString(0) == 'Message sent successfully' From a3f5d4f120014f74acbf655be35444fcdcd2795e Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 16:29:44 -0600 Subject: [PATCH 03/26] fix some tests --- tests/API/test_API.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/API/test_API.py b/tests/API/test_API.py index e3a7641f..bdabac1a 100644 --- a/tests/API/test_API.py +++ b/tests/API/test_API.py @@ -7,9 +7,6 @@ from Hologram.Api import Api from Exceptions.HologramError import ApiError - -credentials = {'devicekey':'12345678'} - class TestHologramAPI: def test_create_no_creds(self): @@ -36,28 +33,28 @@ def test_activate(self, r_post): assert response == {} @patch('requests.post') - def test_activate_bad_status_code(self, r_post): + def test_activate_failed(self, r_post): api = Api(apikey='123apikey') - r_post.return_value = Mock( - status_code=429, - text = 'Too many requests') + r_post.return_value.json = Mock(return_value={"success": False, 'data': {'iccid': 'Activation failed'}}) success, response = api.activateSIM('iccid') assert success == False - assert response == 'Too many requests' + assert response == 'Activation failed' @patch('requests.post') - def test_activate_failed(self, r_post): + def test_activate_bad_status_code(self, r_post): api = Api(apikey='123apikey') - r_post.return_value.json = Mock(return_value={"success": False, 'data': {'iccid': 'Activation failed'}}) + r_post.return_value = Mock( + status_code=429, + text = 'Too many requests') success, response = api.activateSIM('iccid') assert success == False - assert response == 'Activation failed' + assert response == 'Too many requests' @patch('requests.get') def test_get_plans(self, r_post): @@ -79,5 +76,5 @@ def test_get_sim_state(self, r_post): success, response = api.getSIMState('iccid') assert success == True - assert response == {'id': 1, 'orgid': 1} + assert response == 'LIVE' From ff246d318535789057096f02022fff94e49e1917 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 16:33:28 -0600 Subject: [PATCH 04/26] return the status --- Hologram/Api/Api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hologram/Api/Api.py b/Hologram/Api/Api.py index acb28e09..baa8ed40 100644 --- a/Hologram/Api/Api.py +++ b/Hologram/Api/Api.py @@ -44,7 +44,7 @@ def activateSIM(self, sim='', plan=None, zone=1, preview=False): response = requests.post(endpoint, **args) #pylint: disable=no-member if response.status_code != requests.codes.ok: - return (False, response.text) + return (response.status_code, response.text) response = response.json() if not response['success']: From 12519c2a6ae13d154e4aa75b4663a24e4dc1f3c8 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 16:36:40 -0600 Subject: [PATCH 05/26] try specifying status code too --- Hologram/Api/Api.py | 2 +- tests/API/test_API.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Hologram/Api/Api.py b/Hologram/Api/Api.py index baa8ed40..acb28e09 100644 --- a/Hologram/Api/Api.py +++ b/Hologram/Api/Api.py @@ -44,7 +44,7 @@ def activateSIM(self, sim='', plan=None, zone=1, preview=False): response = requests.post(endpoint, **args) #pylint: disable=no-member if response.status_code != requests.codes.ok: - return (response.status_code, response.text) + return (False, response.text) response = response.json() if not response['success']: diff --git a/tests/API/test_API.py b/tests/API/test_API.py index bdabac1a..6d7c9896 100644 --- a/tests/API/test_API.py +++ b/tests/API/test_API.py @@ -25,6 +25,7 @@ def test_create_missing_username(self): def test_activate(self, r_post): api = Api(apikey='123apikey') + r_post.return_value = Mock(status_code=200) r_post.return_value.json = Mock(return_value={"success": True, 'order_data': {}}) success, response = api.activateSIM('iccid') @@ -36,6 +37,7 @@ def test_activate(self, r_post): def test_activate_failed(self, r_post): api = Api(apikey='123apikey') + r_post.return_value = Mock(status_code=200) r_post.return_value.json = Mock(return_value={"success": False, 'data': {'iccid': 'Activation failed'}}) success, response = api.activateSIM('iccid') From 202350a08d97784d4ab4a4c2db598e7595badc09 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 16:55:36 -0600 Subject: [PATCH 06/26] dedupe code --- Hologram/Network/Modem/BG96.py | 165 +--------------------------- Hologram/Network/Modem/EC21.py | 154 +------------------------- Hologram/Network/Modem/Quectel.py | 173 ++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 314 deletions(-) create mode 100644 Hologram/Network/Modem/Quectel.py diff --git a/Hologram/Network/Modem/BG96.py b/Hologram/Network/Modem/BG96.py index f4893cce..b02ac83c 100644 --- a/Hologram/Network/Modem/BG96.py +++ b/Hologram/Network/Modem/BG96.py @@ -7,19 +7,14 @@ # # LICENSE: Distributed under the terms of the MIT License # -import binascii -import time -from serial.serialutil import Timeout - -from Hologram.Network.Modem import Modem +from Hologram.Network.Modem import Quectel from Hologram.Event import Event from UtilClasses import ModemResult -from Exceptions.HologramError import SerialError, NetworkError DEFAULT_BG96_TIMEOUT = 200 -class BG96(Modem): +class BG96(Quectel): usb_ids = [('2c7c', '0296')] def __init__(self, device_name=None, baud_rate='9600', @@ -27,164 +22,10 @@ def __init__(self, device_name=None, baud_rate='9600', super().__init__(device_name=device_name, baud_rate=baud_rate, chatscript_file=chatscript_file, event=event) - self._at_sockets_available = True - self.urc_response = '' - + def connect(self, timeout=DEFAULT_BG96_TIMEOUT): - success = super().connect(timeout) - # put serial mode on other port - # if success is True: - # # detect another open serial port to use for PPP - # devices = self.detect_usable_serial_port() - # if not devices: - # raise SerialError('Not enough serial ports detected for Nova') - # self.logger.debug('Moving connection to port %s', devices[0]) - # self.device_name = devices[0] - # super().initialize_serial_interface() - - return success - - def send_message(self, data, timeout=Modem.DEFAULT_SEND_TIMEOUT): - # Waiting for the open socket urc - while self.urc_state != Modem.SOCKET_WRITE_STATE: - self.checkURC() - - self.write_socket(data) - - loop_timeout = Timeout(timeout) - while self.urc_state != Modem.SOCKET_SEND_READ: - self.checkURC() - if self.urc_state != Modem.SOCKET_SEND_READ: - if loop_timeout.expired(): - raise SerialError('Timeout occurred waiting for message status') - time.sleep(self._RETRY_DELAY) - elif self.urc_state == Modem.SOCKET_CLOSED: - return '[1,0]' #this is connection closed for hologram cloud response - - return self.urc_response - - def create_socket(self): - self._set_up_pdp_context() - - def connect_socket(self, host, port): - self.command('+QIOPEN', '1,0,\"TCP\",\"%s\",%d,0,1' % (host, port)) - # According to the BG96 Docs - # Have to wait for URC response “+QIOPEN: ,” - - def close_socket(self, socket_identifier=None): - ok, _ = self.command('+QICLOSE', self.socket_identifier) - if ok != ModemResult.OK: - self.logger.error('Failed to close socket') - self.urc_state = Modem.SOCKET_CLOSED - self._tear_down_pdp_context() - - def write_socket(self, data): - hexdata = binascii.hexlify(data) - # We have to do it in chunks of 510 since 512 is actually too long (CMEE error) - # and we need 2n chars for hexified data - for chunk in self._chunks(hexdata, 510): - value = '%d,\"%s\"' % (self.socket_identifier, chunk.decode()) - ok, _ = self.set('+QISENDEX', value, timeout=10) - if ok != ModemResult.OK: - self.logger.error('Failed to write to socket') - raise NetworkError('Failed to write to socket') - - def read_socket(self, socket_identifier=None, payload_length=None): - - if socket_identifier is None: - socket_identifier = self.socket_identifier - - if payload_length is None: - payload_length = self.last_read_payload_length - - ok, resp = self.set('+QIRD', '%d,%d' % (socket_identifier, payload_length)) - if ok == ModemResult.OK: - resp = resp.lstrip('+QIRD: ') - if resp is not None: - resp = resp.strip('"') - try: - resp = resp.decode() - except: - # This is some sort of binary data that can't be decoded so just - # return the bytes. We might want to make this happen via parameter - # in the future so it is more deterministic - self.logger.debug('Could not decode recieved data') - - return resp - - def is_registered(self): - return self.check_registered('+CREG') or self.check_registered('+CGREG') - - # EFFECTS: Handles URC related AT command responses. - def handleURC(self, urc): - if urc.startswith('+QIOPEN: '): - response_list = urc.lstrip('+QIOPEN: ').split(',') - socket_identifier = int(response_list[0]) - err = int(response_list[-1]) - if err == 0: - self.urc_state = Modem.SOCKET_WRITE_STATE - self.socket_identifier = socket_identifier - else: - self.logger.error('Failed to open socket') - raise NetworkError('Failed to open socket') - return - if urc.startswith('+QIURC: '): - response_list = urc.lstrip('+QIURC: ').split(',') - urctype = response_list[0] - if urctype == '\"recv\"': - self.urc_state = Modem.SOCKET_SEND_READ - self.socket_identifier = int(response_list[1]) - self.last_read_payload_length = int(response_list[2]) - self.urc_response = self._readline_from_serial_port(5) - if urctype == '\"closed\"': - self.urc_state = Modem.SOCKET_CLOSED - self.socket_identifier = int(response_list[-1]) - return - super().handleURC(urc) - - def _is_pdp_context_active(self): - if not self.is_registered(): - return False - - ok, r = self.command('+QIACT?') - if ok == ModemResult.OK: - try: - pdpstatus = int(r.lstrip('+QIACT: ').split(',')[1]) - # 1: PDP active - return pdpstatus == 1 - except (IndexError, ValueError) as e: - self.logger.error(repr(e)) - except AttributeError as e: - self.logger.error(repr(e)) - return False - - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.set_timezone_configs() - #self.command("+CPIN", "") #set SIM PIN - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() - self.set_network_registration_status() - - def set_network_registration_status(self): - self.command("+CREG", "2") - self.command("+CGREG", "2") - - def _set_up_pdp_context(self): - if self._is_pdp_context_active(): return True - self.logger.info('Setting up PDP context') - self.set('+QICSGP', f'1,1,\"{self._apn}\",\"\",\"\",1') - ok, _ = self.set('+QIACT', '1', timeout=30) - if ok != ModemResult.OK: - self.logger.error('PDP Context setup failed') - raise NetworkError('Failed PDP context setup') - else: - self.logger.info('PDP context active') - def _tear_down_pdp_context(self): if not self._is_pdp_context_active(): return True self.logger.info('Tearing down PDP context') diff --git a/Hologram/Network/Modem/EC21.py b/Hologram/Network/Modem/EC21.py index 5bb0d8a7..0e1323f5 100644 --- a/Hologram/Network/Modem/EC21.py +++ b/Hologram/Network/Modem/EC21.py @@ -7,19 +7,14 @@ # # LICENSE: Distributed under the terms of the MIT License # -import binascii -import time -from serial.serialutil import Timeout - -from Hologram.Network.Modem import Modem +from Hologram.Network.Modem import Quectel from Hologram.Event import Event from UtilClasses import ModemResult -from Exceptions.HologramError import SerialError, NetworkError DEFAULT_EC21_TIMEOUT = 200 -class EC21(Modem): +class EC21(Quectel): usb_ids = [('2c7c', '0121')] def __init__(self, device_name=None, baud_rate='9600', @@ -27,156 +22,11 @@ def __init__(self, device_name=None, baud_rate='9600', super().__init__(device_name=device_name, baud_rate=baud_rate, chatscript_file=chatscript_file, event=event) - self._at_sockets_available = True - self.urc_response = '' def connect(self, timeout=DEFAULT_EC21_TIMEOUT): success = super().connect(timeout) return success - def send_message(self, data, timeout=Modem.DEFAULT_SEND_TIMEOUT): - # Waiting for the open socket urc - while self.urc_state != Modem.SOCKET_WRITE_STATE: - self.checkURC() - - self.write_socket(data) - - loop_timeout = Timeout(timeout) - while self.urc_state != Modem.SOCKET_SEND_READ: - self.checkURC() - if self.urc_state != Modem.SOCKET_SEND_READ: - if loop_timeout.expired(): - raise SerialError('Timeout occurred waiting for message status') - time.sleep(self._RETRY_DELAY) - elif self.urc_state == Modem.SOCKET_CLOSED: - return '[1,0]' #this is connection closed for hologram cloud response - - return self.urc_response.rstrip('\r\n') - - def create_socket(self): - self._set_up_pdp_context() - - def connect_socket(self, host, port): - self.command('+QIOPEN', '1,0,\"TCP\",\"%s\",%d,0,1' % (host, port)) - # According to the EC21 Docs - # Have to wait for URC response “+QIOPEN: ,” - - def close_socket(self, socket_identifier=None): - ok, _ = self.command('+QICLOSE', self.socket_identifier) - if ok != ModemResult.OK: - self.logger.error('Failed to close socket') - self.urc_state = Modem.SOCKET_CLOSED - self._tear_down_pdp_context() - - def write_socket(self, data): - hexdata = binascii.hexlify(data) - # We have to do it in chunks of 510 since 512 is actually too long (CMEE error) - # and we need 2n chars for hexified data - for chunk in self._chunks(hexdata, 510): - value = '%d,\"%s\"' % (self.socket_identifier, chunk.decode()) - ok, _ = self.set('+QISENDEX', value, timeout=10) - if ok != ModemResult.OK: - self.logger.error('Failed to write to socket') - raise NetworkError('Failed to write to socket') - - def read_socket(self, socket_identifier=None, payload_length=None): - - if socket_identifier is None: - socket_identifier = self.socket_identifier - - if payload_length is None: - payload_length = self.last_read_payload_length - - ok, resp = self.set('+QIRD', '%d,%d' % (socket_identifier, payload_length)) - if ok == ModemResult.OK: - resp = resp.lstrip('+QIRD: ') - if resp is not None: - resp = resp.strip('"') - try: - resp = resp.decode() - except: - # This is some sort of binary data that can't be decoded so just - # return the bytes. We might want to make this happen via parameter - # in the future so it is more deterministic - self.logger.debug('Could not decode recieved data') - - return resp - - def listen_socket(self, port): - # No equivilent exists for quectel modems - pass - - def is_registered(self): - return self.check_registered('+CREG') or self.check_registered('+CEREG') - - # EFFECTS: Handles URC related AT command responses. - def handleURC(self, urc): - if urc.startswith('+QIOPEN: '): - response_list = urc.lstrip('+QIOPEN: ').split(',') - socket_identifier = int(response_list[0]) - err = int(response_list[-1]) - if err == 0: - self.urc_state = Modem.SOCKET_WRITE_STATE - self.socket_identifier = socket_identifier - else: - self.logger.error('Failed to open socket') - raise NetworkError('Failed to open socket') - return - if urc.startswith('+QIURC: '): - response_list = urc.lstrip('+QIURC: ').split(',') - urctype = response_list[0] - if urctype == '\"recv\"': - self.urc_state = Modem.SOCKET_SEND_READ - self.socket_identifier = int(response_list[1]) - self.last_read_payload_length = int(response_list[2]) - self.urc_response = self._readline_from_serial_port(5) - if urctype == '\"closed\"': - self.urc_state = Modem.SOCKET_CLOSED - self.socket_identifier = int(response_list[-1]) - return - super().handleURC(urc) - - def _is_pdp_context_active(self): - if not self.is_registered(): - return False - - ok, r = self.command('+QIACT?') - if ok == ModemResult.OK: - try: - pdpstatus = int(r.lstrip('+QIACT: ').split(',')[1]) - # 1: PDP active - return pdpstatus == 1 - except (IndexError, ValueError) as e: - self.logger.error(repr(e)) - except AttributeError as e: - self.logger.error(repr(e)) - return False - - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.set_timezone_configs() - #self.command("+CPIN", "") #set SIM PIN - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() - self.set_network_registration_status() - - def set_network_registration_status(self): - self.command("+CREG", "2") - self.command("+CEREG", "2") - - def _set_up_pdp_context(self): - if self._is_pdp_context_active(): return True - self.logger.info('Setting up PDP context') - self.set('+QICSGP', f'1,1,\"{self._apn}\",\"\",\"\",1') - ok, _ = self.set('+QIACT', '1', timeout=30) - if ok != ModemResult.OK: - self.logger.error('PDP Context setup failed') - raise NetworkError('Failed PDP context setup') - else: - self.logger.info('PDP context active') - def _tear_down_pdp_context(self): if not self._is_pdp_context_active(): return True self.logger.info('Tearing down PDP context') diff --git a/Hologram/Network/Modem/Quectel.py b/Hologram/Network/Modem/Quectel.py new file mode 100644 index 00000000..ea6fbda0 --- /dev/null +++ b/Hologram/Network/Modem/Quectel.py @@ -0,0 +1,173 @@ +# Quectel.py - Hologram Python SDK Quectel modem interface +# +# Author: Hologram +# +# Copyright 2016 - Hologram (Konekt, Inc.) +# +# +# LICENSE: Distributed under the terms of the MIT License +# +import time +import binascii + +from serial.serialutil import Timeout + +from Hologram.Network.Modem import Modem +from Hologram.Event import Event +from UtilClasses import ModemResult +from Exceptions.HologramError import SerialError, NetworkError + +DEFAULT_NOVA_TIMEOUT = 200 + +class Quectel(Modem): + + def __init__(self, device_name=None, baud_rate='9600', + chatscript_file=None, event=Event()): + + super().__init__(device_name=device_name, baud_rate=baud_rate, + chatscript_file=chatscript_file, event=event) + self._at_sockets_available = True + self.urc_response = '' + + def send_message(self, data, timeout=Modem.DEFAULT_SEND_TIMEOUT): + # Waiting for the open socket urc + while self.urc_state != Modem.SOCKET_WRITE_STATE: + self.checkURC() + + self.write_socket(data) + + loop_timeout = Timeout(timeout) + while self.urc_state != Modem.SOCKET_SEND_READ: + self.checkURC() + if self.urc_state != Modem.SOCKET_SEND_READ: + if loop_timeout.expired(): + raise SerialError('Timeout occurred waiting for message status') + time.sleep(self._RETRY_DELAY) + elif self.urc_state == Modem.SOCKET_CLOSED: + return '[1,0]' #this is connection closed for hologram cloud response + + return self.urc_response.rstrip('\r\n') + + def create_socket(self): + self._set_up_pdp_context() + + def connect_socket(self, host, port): + self.command('+QIOPEN', '1,0,\"TCP\",\"%s\",%d,0,1' % (host, port)) + # According to the Quectel Docs + # Have to wait for URC response “+QIOPEN: ,” + + def close_socket(self, socket_identifier=None): + ok, _ = self.command('+QICLOSE', self.socket_identifier) + if ok != ModemResult.OK: + self.logger.error('Failed to close socket') + self.urc_state = Modem.SOCKET_CLOSED + self._tear_down_pdp_context() + + def write_socket(self, data): + hexdata = binascii.hexlify(data) + # We have to do it in chunks of 510 since 512 is actually too long (CMEE error) + # and we need 2n chars for hexified data + for chunk in self._chunks(hexdata, 510): + value = '%d,\"%s\"' % (self.socket_identifier, chunk.decode()) + ok, _ = self.set('+QISENDEX', value, timeout=10) + if ok != ModemResult.OK: + self.logger.error('Failed to write to socket') + raise NetworkError('Failed to write to socket') + + def read_socket(self, socket_identifier=None, payload_length=None): + + if socket_identifier is None: + socket_identifier = self.socket_identifier + + if payload_length is None: + payload_length = self.last_read_payload_length + + ok, resp = self.set('+QIRD', '%d,%d' % (socket_identifier, payload_length)) + if ok == ModemResult.OK: + resp = resp.lstrip('+QIRD: ') + if resp is not None: + resp = resp.strip('"') + try: + resp = resp.decode() + except: + # This is some sort of binary data that can't be decoded so just + # return the bytes. We might want to make this happen via parameter + # in the future so it is more deterministic + self.logger.debug('Could not decode recieved data') + + return resp + + def listen_socket(self, port): + # No equivilent exists for quectel modems + pass + + def is_registered(self): + return self.check_registered('+CREG') or self.check_registered('+CEREG') + + # EFFECTS: Handles URC related AT command responses. + def handleURC(self, urc): + if urc.startswith('+QIOPEN: '): + response_list = urc.lstrip('+QIOPEN: ').split(',') + socket_identifier = int(response_list[0]) + err = int(response_list[-1]) + if err == 0: + self.urc_state = Modem.SOCKET_WRITE_STATE + self.socket_identifier = socket_identifier + else: + self.logger.error('Failed to open socket') + raise NetworkError('Failed to open socket') + return + if urc.startswith('+QIURC: '): + response_list = urc.lstrip('+QIURC: ').split(',') + urctype = response_list[0] + if urctype == '\"recv\"': + self.urc_state = Modem.SOCKET_SEND_READ + self.socket_identifier = int(response_list[1]) + self.last_read_payload_length = int(response_list[2]) + self.urc_response = self._readline_from_serial_port(5) + if urctype == '\"closed\"': + self.urc_state = Modem.SOCKET_CLOSED + self.socket_identifier = int(response_list[-1]) + return + super().handleURC(urc) + + def _is_pdp_context_active(self): + if not self.is_registered(): + return False + + ok, r = self.command('+QIACT?') + if ok == ModemResult.OK: + try: + pdpstatus = int(r.lstrip('+QIACT: ').split(',')[1]) + # 1: PDP active + return pdpstatus == 1 + except (IndexError, ValueError) as e: + self.logger.error(repr(e)) + except AttributeError as e: + self.logger.error(repr(e)) + return False + + def init_serial_commands(self): + self.command("E0") #echo off + self.command("+CMEE", "2") #set verbose error codes + self.command("+CPIN?") + self.set_timezone_configs() + #self.command("+CPIN", "") #set SIM PIN + self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") + self.set_sms_configs() + self.set_network_registration_status() + + def set_network_registration_status(self): + self.command("+CREG", "2") + self.command("+CEREG", "2") + + def _set_up_pdp_context(self): + if self._is_pdp_context_active(): return True + self.logger.info('Setting up PDP context') + self.set('+QICSGP', f'1,1,\"{self._apn}\",\"\",\"\",1') + ok, _ = self.set('+QIACT', '1', timeout=30) + if ok != ModemResult.OK: + self.logger.error('PDP Context setup failed') + raise NetworkError('Failed PDP context setup') + else: + self.logger.info('PDP context active') \ No newline at end of file From 3ff19323cb779d9ec9c86fad89798ff3fa1c37b8 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 17:02:07 -0600 Subject: [PATCH 07/26] __init errors --- Hologram/Network/Modem/BG96.py | 7 +------ Hologram/Network/Modem/EC21.py | 6 ------ Hologram/Network/Modem/Quectel.py | 2 +- Hologram/Network/Modem/__init__.py | 2 +- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Hologram/Network/Modem/BG96.py b/Hologram/Network/Modem/BG96.py index b02ac83c..4ce86972 100644 --- a/Hologram/Network/Modem/BG96.py +++ b/Hologram/Network/Modem/BG96.py @@ -16,15 +16,10 @@ class BG96(Quectel): usb_ids = [('2c7c', '0296')] - - def __init__(self, device_name=None, baud_rate='9600', - chatscript_file=None, event=Event()): - - super().__init__(device_name=device_name, baud_rate=baud_rate, - chatscript_file=chatscript_file, event=event) def connect(self, timeout=DEFAULT_BG96_TIMEOUT): success = super().connect(timeout) + return success def _tear_down_pdp_context(self): if not self._is_pdp_context_active(): return True diff --git a/Hologram/Network/Modem/EC21.py b/Hologram/Network/Modem/EC21.py index 0e1323f5..0d44c12d 100644 --- a/Hologram/Network/Modem/EC21.py +++ b/Hologram/Network/Modem/EC21.py @@ -17,12 +17,6 @@ class EC21(Quectel): usb_ids = [('2c7c', '0121')] - def __init__(self, device_name=None, baud_rate='9600', - chatscript_file=None, event=Event()): - - super().__init__(device_name=device_name, baud_rate=baud_rate, - chatscript_file=chatscript_file, event=event) - def connect(self, timeout=DEFAULT_EC21_TIMEOUT): success = super().connect(timeout) return success diff --git a/Hologram/Network/Modem/Quectel.py b/Hologram/Network/Modem/Quectel.py index ea6fbda0..11e1a8a8 100644 --- a/Hologram/Network/Modem/Quectel.py +++ b/Hologram/Network/Modem/Quectel.py @@ -25,7 +25,7 @@ def __init__(self, device_name=None, baud_rate='9600', chatscript_file=None, event=Event()): super().__init__(device_name=device_name, baud_rate=baud_rate, - chatscript_file=chatscript_file, event=event) + chatscript_file=chatscript_file, event=event) self._at_sockets_available = True self.urc_response = '' diff --git a/Hologram/Network/Modem/__init__.py b/Hologram/Network/Modem/__init__.py index 84491b79..1e795a17 100644 --- a/Hologram/Network/Modem/__init__.py +++ b/Hologram/Network/Modem/__init__.py @@ -1,4 +1,4 @@ -__all__ = ['Modem', 'MockModem', 'MS2131', 'Nova', 'E303', 'E372', 'EC21', 'BG96'] +__all__ = ['Modem', 'MockModem', 'MS2131', 'Nova', 'E303', 'E372', 'Quectel'] from .IModem import IModem from .Modem import Modem From 41131119d6c2df0791f1afbccf47f444c8992588 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 17:07:16 -0600 Subject: [PATCH 08/26] sdk naming scheme is bad --- Hologram/Network/Modem/BG96.py | 3 +-- Hologram/Network/Modem/EC21.py | 3 +-- Hologram/Network/Modem/Quectel.py | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Hologram/Network/Modem/BG96.py b/Hologram/Network/Modem/BG96.py index 4ce86972..dafa1ef4 100644 --- a/Hologram/Network/Modem/BG96.py +++ b/Hologram/Network/Modem/BG96.py @@ -8,8 +8,7 @@ # LICENSE: Distributed under the terms of the MIT License # -from Hologram.Network.Modem import Quectel -from Hologram.Event import Event +from Hologram.Network.Modem.Quectel import Quectel from UtilClasses import ModemResult DEFAULT_BG96_TIMEOUT = 200 diff --git a/Hologram/Network/Modem/EC21.py b/Hologram/Network/Modem/EC21.py index 0d44c12d..45963636 100644 --- a/Hologram/Network/Modem/EC21.py +++ b/Hologram/Network/Modem/EC21.py @@ -8,8 +8,7 @@ # LICENSE: Distributed under the terms of the MIT License # -from Hologram.Network.Modem import Quectel -from Hologram.Event import Event +from Hologram.Network.Modem.Quectel import Quectel from UtilClasses import ModemResult DEFAULT_EC21_TIMEOUT = 200 diff --git a/Hologram/Network/Modem/Quectel.py b/Hologram/Network/Modem/Quectel.py index 11e1a8a8..be99d11c 100644 --- a/Hologram/Network/Modem/Quectel.py +++ b/Hologram/Network/Modem/Quectel.py @@ -17,8 +17,6 @@ from UtilClasses import ModemResult from Exceptions.HologramError import SerialError, NetworkError -DEFAULT_NOVA_TIMEOUT = 200 - class Quectel(Modem): def __init__(self, device_name=None, baud_rate='9600', From 7b26c9b0e50b3e221a4529ccad519d5625a0f0b9 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 17:13:57 -0600 Subject: [PATCH 09/26] dedupe more code --- Hologram/Network/Modem/Nova.py | 8 ++++++++ Hologram/Network/Modem/NovaM.py | 9 --------- Hologram/Network/Modem/Nova_U201.py | 8 +------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Hologram/Network/Modem/Nova.py b/Hologram/Network/Modem/Nova.py index c3ddd031..8ed3227f 100644 --- a/Hologram/Network/Modem/Nova.py +++ b/Hologram/Network/Modem/Nova.py @@ -27,6 +27,14 @@ def disable_at_sockets_mode(self): def enable_at_sockets_mode(self): self._at_sockets_available = True + def init_serial_commands(self): + self.command("E0") #echo off + self.command("+CMEE", "2") #set verbose error codes + self.command("+CPIN?") + self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") + self.set_sms_configs() + self.set_network_registration_status() + @property def version(self): return self._basic_command('I9') diff --git a/Hologram/Network/Modem/NovaM.py b/Hologram/Network/Modem/NovaM.py index 9fbb95ec..65e6750d 100644 --- a/Hologram/Network/Modem/NovaM.py +++ b/Hologram/Network/Modem/NovaM.py @@ -33,15 +33,6 @@ def __init__(self, device_name=None, baud_rate='9600', else: self.is_r410 = True - - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() - self.set_network_registration_status() - def set_network_registration_status(self): self.command("+CEREG", "2") diff --git a/Hologram/Network/Modem/Nova_U201.py b/Hologram/Network/Modem/Nova_U201.py index b3e3ddc3..d3111b80 100644 --- a/Hologram/Network/Modem/Nova_U201.py +++ b/Hologram/Network/Modem/Nova_U201.py @@ -72,14 +72,8 @@ def enforce_nova_modem_mode(self): super().initialize_serial_interface() def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") + super().init_serial_commands() self.set_timezone_configs() - #self.command("+CPIN", "") #set SIM PIN - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() - self.set_network_registration_status() def set_network_registration_status(self): self.command("+CREG", "2") From 55470db3fa43e7ea5bbfa34c5368e05f9e1babbd Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Mon, 14 Nov 2022 17:23:15 -0600 Subject: [PATCH 10/26] add tests for the EC21 and BG96 --- tests/Modem/test_BG96.py | 69 ++++++++++++++++++++++++++++++++++++++++ tests/Modem/test_EC21.py | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 tests/Modem/test_BG96.py create mode 100644 tests/Modem/test_EC21.py diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py new file mode 100644 index 00000000..ac97603f --- /dev/null +++ b/tests/Modem/test_BG96.py @@ -0,0 +1,69 @@ +# Author: Hologram +# +# Copyright 2017 - Hologram (Konekt, Inc.) +# +# LICENSE: Distributed under the terms of the MIT License +# +# test_BG96.py - This file implements unit tests for the BG96 modem interface. + +from mock import call, patch +import pytest +import sys + +from Hologram.Network.Modem.BG96 import BG96 + +sys.path.append(".") +sys.path.append("..") +sys.path.append("../..") + +def mock_write(modem, message): + return True + +def mock_read(modem): + return True + +def mock_readline(modem, timeout=None, hide=False): + return '' + +def mock_open_serial_port(modem, device_name=None): + return True + +def mock_close_serial_port(modem): + return True + +def mock_detect_usable_serial_port(modem, stop_on_first=True): + return '/dev/ttyUSB0' + +@pytest.fixture +def no_serial_port(monkeypatch): + monkeypatch.setattr(BG96, '_read_from_serial_port', mock_read) + monkeypatch.setattr(BG96, '_readline_from_serial_port', mock_readline) + monkeypatch.setattr(BG96, '_write_to_serial_port_and_flush', mock_write) + monkeypatch.setattr(BG96, 'openSerialPort', mock_open_serial_port) + monkeypatch.setattr(BG96, 'closeSerialPort', mock_close_serial_port) + monkeypatch.setattr(BG96, 'detect_usable_serial_port', mock_detect_usable_serial_port) + +def test_init_BG96_no_args(no_serial_port): + modem = BG96() + assert(modem.timeout == 1) + assert(modem.socket_identifier == 0) + assert(modem.chatscript_file.endswith('/chatscripts/default-script')) + assert(modem._at_sockets_available) + +@patch.object(BG96, 'check_registered') +def test_is_registered(mock_check_registered, no_serial_port): + modem = BG96() + mock_check_registered.return_value = False + mock_check_registered.reset_mock() + assert(modem.is_registered() == False) + calls = [call('+CREG'), call('+CEREG')] + mock_check_registered.assert_has_calls(calls, any_order=True) + +@patch.object(BG96, 'command') +def test_set_network_registration_status(mock_command, no_serial_port): + modem = BG96() + mock_command.return_value = [] + mock_command.reset_mock() + modem.set_network_registration_status() + calls = [call('+CREG', '2'), call('+CEREG', '2')] + mock_command.assert_has_calls(calls) diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py new file mode 100644 index 00000000..145a55f6 --- /dev/null +++ b/tests/Modem/test_EC21.py @@ -0,0 +1,69 @@ +# Author: Hologram +# +# Copyright 2017 - Hologram (Konekt, Inc.) +# +# LICENSE: Distributed under the terms of the MIT License +# +# test_EC21.py - This file implements unit tests for the EC21 modem interface. + +from mock import call, patch +import pytest +import sys + +from Hologram.Network.Modem.EC21 import EC21 + +sys.path.append(".") +sys.path.append("..") +sys.path.append("../..") + +def mock_write(modem, message): + return True + +def mock_read(modem): + return True + +def mock_readline(modem, timeout=None, hide=False): + return '' + +def mock_open_serial_port(modem, device_name=None): + return True + +def mock_close_serial_port(modem): + return True + +def mock_detect_usable_serial_port(modem, stop_on_first=True): + return '/dev/ttyUSB0' + +@pytest.fixture +def no_serial_port(monkeypatch): + monkeypatch.setattr(EC21, '_read_from_serial_port', mock_read) + monkeypatch.setattr(EC21, '_readline_from_serial_port', mock_readline) + monkeypatch.setattr(EC21, '_write_to_serial_port_and_flush', mock_write) + monkeypatch.setattr(EC21, 'openSerialPort', mock_open_serial_port) + monkeypatch.setattr(EC21, 'closeSerialPort', mock_close_serial_port) + monkeypatch.setattr(EC21, 'detect_usable_serial_port', mock_detect_usable_serial_port) + +def test_init_EC21_no_args(no_serial_port): + modem = EC21() + assert(modem.timeout == 1) + assert(modem.socket_identifier == 0) + assert(modem.chatscript_file.endswith('/chatscripts/default-script')) + assert(modem._at_sockets_available) + +@patch.object(EC21, 'check_registered') +def test_is_registered(mock_check_registered, no_serial_port): + modem = EC21() + mock_check_registered.return_value = False + mock_check_registered.reset_mock() + assert(modem.is_registered() == False) + calls = [call('+CREG'), call('+CEREG')] + mock_check_registered.assert_has_calls(calls, any_order=True) + +@patch.object(EC21, 'command') +def test_set_network_registration_status(mock_command, no_serial_port): + modem = EC21() + mock_command.return_value = [] + mock_command.reset_mock() + modem.set_network_registration_status() + calls = [call('+CREG', '2'), call('+CEREG', '2')] + mock_command.assert_has_calls(calls) From 53921ef3dc11ae2c583a806eee718d24f387b940 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 11:21:23 -0600 Subject: [PATCH 11/26] more tests --- Hologram/Network/Modem/BG96.py | 3 +- tests/Modem/test_BG96.py | 26 +++++------- tests/Modem/test_EC21.py | 25 ++++------- tests/Modem/test_Quectel.py | 78 ++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 tests/Modem/test_Quectel.py diff --git a/Hologram/Network/Modem/BG96.py b/Hologram/Network/Modem/BG96.py index dafa1ef4..fc1b4edf 100644 --- a/Hologram/Network/Modem/BG96.py +++ b/Hologram/Network/Modem/BG96.py @@ -17,8 +17,7 @@ class BG96(Quectel): usb_ids = [('2c7c', '0296')] def connect(self, timeout=DEFAULT_BG96_TIMEOUT): - success = super().connect(timeout) - return success + return super().connect(timeout) def _tear_down_pdp_context(self): if not self._is_pdp_context_active(): return True diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py index ac97603f..631b2f60 100644 --- a/tests/Modem/test_BG96.py +++ b/tests/Modem/test_BG96.py @@ -6,11 +6,12 @@ # # test_BG96.py - This file implements unit tests for the BG96 modem interface. -from mock import call, patch +from unittest import patch import pytest import sys from Hologram.Network.Modem.BG96 import BG96 +from UtilClasses import ModemResult sys.path.append(".") sys.path.append("..") @@ -50,20 +51,13 @@ def test_init_BG96_no_args(no_serial_port): assert(modem.chatscript_file.endswith('/chatscripts/default-script')) assert(modem._at_sockets_available) -@patch.object(BG96, 'check_registered') -def test_is_registered(mock_check_registered, no_serial_port): - modem = BG96() - mock_check_registered.return_value = False - mock_check_registered.reset_mock() - assert(modem.is_registered() == False) - calls = [call('+CREG'), call('+CEREG')] - mock_check_registered.assert_has_calls(calls, any_order=True) - +@patch.object(BG96, 'set') @patch.object(BG96, 'command') -def test_set_network_registration_status(mock_command, no_serial_port): +def test_close_socket(mock_set, mock_command, no_serial_port): modem = BG96() - mock_command.return_value = [] - mock_command.reset_mock() - modem.set_network_registration_status() - calls = [call('+CREG', '2'), call('+CEREG', '2')] - mock_command.assert_has_calls(calls) + modem.socket_identifier = 1 + mock_set.set.return_value = (ModemResult.OK, None) + assert(modem.close_socket() == False) + mock_set.assert_called_with('+QIACT', '0', timeout=30) + mock_command.assert_called_with('+QICLOSE', 1) + diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py index 145a55f6..ad009d75 100644 --- a/tests/Modem/test_EC21.py +++ b/tests/Modem/test_EC21.py @@ -6,11 +6,12 @@ # # test_EC21.py - This file implements unit tests for the EC21 modem interface. -from mock import call, patch +from unittest import patch import pytest import sys from Hologram.Network.Modem.EC21 import EC21 +from UtilClasses import ModemResult sys.path.append(".") sys.path.append("..") @@ -50,20 +51,12 @@ def test_init_EC21_no_args(no_serial_port): assert(modem.chatscript_file.endswith('/chatscripts/default-script')) assert(modem._at_sockets_available) -@patch.object(EC21, 'check_registered') -def test_is_registered(mock_check_registered, no_serial_port): - modem = EC21() - mock_check_registered.return_value = False - mock_check_registered.reset_mock() - assert(modem.is_registered() == False) - calls = [call('+CREG'), call('+CEREG')] - mock_check_registered.assert_has_calls(calls, any_order=True) - +@patch.object(EC21, 'set') @patch.object(EC21, 'command') -def test_set_network_registration_status(mock_command, no_serial_port): +def test_close_socket(mock_set, mock_command, no_serial_port): modem = EC21() - mock_command.return_value = [] - mock_command.reset_mock() - modem.set_network_registration_status() - calls = [call('+CREG', '2'), call('+CEREG', '2')] - mock_command.assert_has_calls(calls) + modem.socket_identifier = 1 + mock_set.set.return_value = (ModemResult.OK, None) + assert(modem.close_socket() == False) + mock_set.assert_called_with('+QIDEACT', '1', timeout=30) + mock_command.assert_called_with('+QICLOSE', 1) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py new file mode 100644 index 00000000..e8bfd37f --- /dev/null +++ b/tests/Modem/test_Quectel.py @@ -0,0 +1,78 @@ +# Author: Hologram +# +# Copyright 2017 - Hologram (Konekt, Inc.) +# +# LICENSE: Distributed under the terms of the MIT License +# +# test_Quectel.py - This file implements unit tests for the Quectel modem interface. + +from unittest import patch +import pytest +import sys + +from Hologram.Network.Modem.Quectel import Quectel +from UtilClasses import ModemResult + +sys.path.append(".") +sys.path.append("..") +sys.path.append("../..") + +def mock_write(modem, message): + return True + +def mock_read(modem): + return True + +def mock_readline(modem, timeout=None, hide=False): + return '' + +def mock_open_serial_port(modem, device_name=None): + return True + +def mock_close_serial_port(modem): + return True + +def mock_detect_usable_serial_port(modem, stop_on_first=True): + return '/dev/ttyUSB0' + +@pytest.fixture +def no_serial_port(monkeypatch): + monkeypatch.setattr(Quectel, '_read_from_serial_port', mock_read) + monkeypatch.setattr(Quectel, '_readline_from_serial_port', mock_readline) + monkeypatch.setattr(Quectel, '_write_to_serial_port_and_flush', mock_write) + monkeypatch.setattr(Quectel, 'openSerialPort', mock_open_serial_port) + monkeypatch.setattr(Quectel, 'closeSerialPort', mock_close_serial_port) + monkeypatch.setattr(Quectel, 'detect_usable_serial_port', mock_detect_usable_serial_port) + +def test_init_Quectel_no_args(no_serial_port): + modem = Quectel() + assert(modem.timeout == 1) + assert(modem.socket_identifier == 0) + assert(modem.chatscript_file.endswith('/chatscripts/default-script')) + assert(modem._at_sockets_available) + +@patch.object(Quectel, 'command') +def test_connect_socket(mock_command, no_serial_port): + modem = Quectel() + modem.socket_identifier = 1 + host = 'hologram.io' + port = 9999 + modem.connect_socket(host, port) + mock_command.assert_called_with('+QIOPEN', '1,0,\"TCP\",\"%s\",%d,0,1' % (host, port)) + +@patch.object(Quectel, 'set') +def test_write_socket_small(mock_command, no_serial_port): + modem = Quectel() + modem.socket_identifier = 1 + data = b'Message smaller than 510 bytes' + modem.connect_socket(data) + mock_command.assert_called_with('+QISENDEX', '1,"4d65737361676520736d616c6c6572207468616e20353130206279746573"') + +@patch.object(Quectel, 'set') +def test_write_socket_large(mock_command, no_serial_port): + modem = Quectel() + modem.socket_identifier = 1 + data = b'a'*300 + modem.connect_socket(data) + mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255) + mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45) \ No newline at end of file From e2385c98ed30f38341a5b7d930ef343a9eb26125 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 11:24:01 -0600 Subject: [PATCH 12/26] typo --- tests/Modem/test_Quectel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index e8bfd37f..545eed55 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -74,5 +74,5 @@ def test_write_socket_large(mock_command, no_serial_port): modem.socket_identifier = 1 data = b'a'*300 modem.connect_socket(data) - mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255) - mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45) \ No newline at end of file + mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255)) + mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45)) \ No newline at end of file From cdfbe3eb60f3dd1aa0d1b1fd402d1b0103e8398e Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 11:26:10 -0600 Subject: [PATCH 13/26] fix imports --- tests/Modem/test_BG96.py | 2 +- tests/Modem/test_EC21.py | 2 +- tests/Modem/test_Quectel.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py index 631b2f60..752f9a9f 100644 --- a/tests/Modem/test_BG96.py +++ b/tests/Modem/test_BG96.py @@ -6,7 +6,7 @@ # # test_BG96.py - This file implements unit tests for the BG96 modem interface. -from unittest import patch +from unittest.mock import patch import pytest import sys diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py index ad009d75..a0698e22 100644 --- a/tests/Modem/test_EC21.py +++ b/tests/Modem/test_EC21.py @@ -6,7 +6,7 @@ # # test_EC21.py - This file implements unit tests for the EC21 modem interface. -from unittest import patch +from unittest.mock import patch import pytest import sys diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 545eed55..37ba70d1 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -6,7 +6,7 @@ # # test_Quectel.py - This file implements unit tests for the Quectel modem interface. -from unittest import patch +from unittest.mock import patch import pytest import sys From f29747c53f918f6960171563d3ed444c1f692aad Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 11:28:47 -0600 Subject: [PATCH 14/26] more test fixes --- tests/Modem/test_BG96.py | 2 +- tests/Modem/test_EC21.py | 2 +- tests/Modem/test_Quectel.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py index 752f9a9f..ec01e675 100644 --- a/tests/Modem/test_BG96.py +++ b/tests/Modem/test_BG96.py @@ -56,7 +56,7 @@ def test_init_BG96_no_args(no_serial_port): def test_close_socket(mock_set, mock_command, no_serial_port): modem = BG96() modem.socket_identifier = 1 - mock_set.set.return_value = (ModemResult.OK, None) + mock_set.return_value = (ModemResult.OK, None) assert(modem.close_socket() == False) mock_set.assert_called_with('+QIACT', '0', timeout=30) mock_command.assert_called_with('+QICLOSE', 1) diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py index a0698e22..d56d93ab 100644 --- a/tests/Modem/test_EC21.py +++ b/tests/Modem/test_EC21.py @@ -56,7 +56,7 @@ def test_init_EC21_no_args(no_serial_port): def test_close_socket(mock_set, mock_command, no_serial_port): modem = EC21() modem.socket_identifier = 1 - mock_set.set.return_value = (ModemResult.OK, None) + mock_set.return_value = (ModemResult.OK, None) assert(modem.close_socket() == False) mock_set.assert_called_with('+QIDEACT', '1', timeout=30) mock_command.assert_called_with('+QICLOSE', 1) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 37ba70d1..54a64f2d 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -65,7 +65,7 @@ def test_write_socket_small(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 data = b'Message smaller than 510 bytes' - modem.connect_socket(data) + modem.write_socket(data) mock_command.assert_called_with('+QISENDEX', '1,"4d65737361676520736d616c6c6572207468616e20353130206279746573"') @patch.object(Quectel, 'set') @@ -73,6 +73,6 @@ def test_write_socket_large(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 data = b'a'*300 - modem.connect_socket(data) + modem.write_socket(data) mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255)) mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45)) \ No newline at end of file From d61c891fe91aff2350f02591dcdd00844f95706a Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 11:41:04 -0600 Subject: [PATCH 15/26] mock pdp check --- tests/Modem/test_BG96.py | 6 ++++-- tests/Modem/test_EC21.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py index ec01e675..7696f0ce 100644 --- a/tests/Modem/test_BG96.py +++ b/tests/Modem/test_BG96.py @@ -53,11 +53,13 @@ def test_init_BG96_no_args(no_serial_port): @patch.object(BG96, 'set') @patch.object(BG96, 'command') -def test_close_socket(mock_set, mock_command, no_serial_port): +@patch.object(BG96, '_is_pdp_context_active') +def test_close_socket(mock_set, mock_command, mock_pdp, no_serial_port): modem = BG96() modem.socket_identifier = 1 mock_set.return_value = (ModemResult.OK, None) - assert(modem.close_socket() == False) + mock_pdp.return_value = True + modem.close_socket() == False mock_set.assert_called_with('+QIACT', '0', timeout=30) mock_command.assert_called_with('+QICLOSE', 1) diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py index d56d93ab..e60ef2f1 100644 --- a/tests/Modem/test_EC21.py +++ b/tests/Modem/test_EC21.py @@ -53,10 +53,12 @@ def test_init_EC21_no_args(no_serial_port): @patch.object(EC21, 'set') @patch.object(EC21, 'command') -def test_close_socket(mock_set, mock_command, no_serial_port): +@patch.object(EC21, '_is_pdp_context_active') +def test_close_socket(mock_set, mock_command, mock_pdp, no_serial_port): modem = EC21() modem.socket_identifier = 1 mock_set.return_value = (ModemResult.OK, None) - assert(modem.close_socket() == False) + mock_pdp.return_value = True + modem.close_socket() == False mock_set.assert_called_with('+QIDEACT', '1', timeout=30) mock_command.assert_called_with('+QICLOSE', 1) From 7a36699ed5f919ad5592f6ff6294cc4c0bc6bc08 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 11:46:22 -0600 Subject: [PATCH 16/26] mock command return values --- tests/Modem/test_BG96.py | 3 ++- tests/Modem/test_EC21.py | 3 ++- tests/Modem/test_Quectel.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py index 7696f0ce..a67f818b 100644 --- a/tests/Modem/test_BG96.py +++ b/tests/Modem/test_BG96.py @@ -58,8 +58,9 @@ def test_close_socket(mock_set, mock_command, mock_pdp, no_serial_port): modem = BG96() modem.socket_identifier = 1 mock_set.return_value = (ModemResult.OK, None) + mock_command.return_value = (ModemResult.OK, None) mock_pdp.return_value = True - modem.close_socket() == False + modem.close_socket() mock_set.assert_called_with('+QIACT', '0', timeout=30) mock_command.assert_called_with('+QICLOSE', 1) diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py index e60ef2f1..6947951f 100644 --- a/tests/Modem/test_EC21.py +++ b/tests/Modem/test_EC21.py @@ -58,7 +58,8 @@ def test_close_socket(mock_set, mock_command, mock_pdp, no_serial_port): modem = EC21() modem.socket_identifier = 1 mock_set.return_value = (ModemResult.OK, None) + mock_command.return_value = (ModemResult.OK, None) mock_pdp.return_value = True - modem.close_socket() == False + modem.close_socket() mock_set.assert_called_with('+QIDEACT', '1', timeout=30) mock_command.assert_called_with('+QICLOSE', 1) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 54a64f2d..bdb6a0ab 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -65,6 +65,7 @@ def test_write_socket_small(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 data = b'Message smaller than 510 bytes' + mock_command.return_value = (ModemResult.OK, None) modem.write_socket(data) mock_command.assert_called_with('+QISENDEX', '1,"4d65737361676520736d616c6c6572207468616e20353130206279746573"') @@ -73,6 +74,7 @@ def test_write_socket_large(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 data = b'a'*300 + mock_command.return_value = (ModemResult.OK, None) modem.write_socket(data) mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255)) mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45)) \ No newline at end of file From 68a5aa1d6a12fa4c2baf51c31dfba38714600785 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 12:04:23 -0600 Subject: [PATCH 17/26] the order of mocked objects is dumb --- tests/Modem/test_BG96.py | 2 +- tests/Modem/test_EC21.py | 2 +- tests/Modem/test_Quectel.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py index a67f818b..f1a4d0f2 100644 --- a/tests/Modem/test_BG96.py +++ b/tests/Modem/test_BG96.py @@ -54,7 +54,7 @@ def test_init_BG96_no_args(no_serial_port): @patch.object(BG96, 'set') @patch.object(BG96, 'command') @patch.object(BG96, '_is_pdp_context_active') -def test_close_socket(mock_set, mock_command, mock_pdp, no_serial_port): +def test_close_socket(mock_pdp, mock_command, mock_set, no_serial_port): modem = BG96() modem.socket_identifier = 1 mock_set.return_value = (ModemResult.OK, None) diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py index 6947951f..b127e7a4 100644 --- a/tests/Modem/test_EC21.py +++ b/tests/Modem/test_EC21.py @@ -54,7 +54,7 @@ def test_init_EC21_no_args(no_serial_port): @patch.object(EC21, 'set') @patch.object(EC21, 'command') @patch.object(EC21, '_is_pdp_context_active') -def test_close_socket(mock_set, mock_command, mock_pdp, no_serial_port): +def test_close_socket(mock_pdp, mock_command, mock_set, no_serial_port): modem = EC21() modem.socket_identifier = 1 mock_set.return_value = (ModemResult.OK, None) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index bdb6a0ab..9aafd310 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -67,7 +67,7 @@ def test_write_socket_small(mock_command, no_serial_port): data = b'Message smaller than 510 bytes' mock_command.return_value = (ModemResult.OK, None) modem.write_socket(data) - mock_command.assert_called_with('+QISENDEX', '1,"4d65737361676520736d616c6c6572207468616e20353130206279746573"') + mock_command.assert_called_with('+QISENDEX', '1,"4d65737361676520736d616c6c6572207468616e20353130206279746573"', timeout=10) @patch.object(Quectel, 'set') def test_write_socket_large(mock_command, no_serial_port): @@ -76,5 +76,5 @@ def test_write_socket_large(mock_command, no_serial_port): data = b'a'*300 mock_command.return_value = (ModemResult.OK, None) modem.write_socket(data) - mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255)) - mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45)) \ No newline at end of file + mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255), timeout=10) + mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45), timeout=10) \ No newline at end of file From 0fa3c4249eebe363653a711df38eb9660ca03282 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 12:11:09 -0600 Subject: [PATCH 18/26] format file, add call assertion --- tests/Modem/test_BG96.py | 46 +++++++++++++++----------- tests/Modem/test_EC21.py | 45 +++++++++++++++---------- tests/Modem/test_Quectel.py | 66 ++++++++++++++++++++++++------------- 3 files changed, 97 insertions(+), 60 deletions(-) diff --git a/tests/Modem/test_BG96.py b/tests/Modem/test_BG96.py index f1a4d0f2..681d1e09 100644 --- a/tests/Modem/test_BG96.py +++ b/tests/Modem/test_BG96.py @@ -17,43 +17,52 @@ sys.path.append("..") sys.path.append("../..") + def mock_write(modem, message): return True + def mock_read(modem): return True + def mock_readline(modem, timeout=None, hide=False): - return '' + return "" + def mock_open_serial_port(modem, device_name=None): return True + def mock_close_serial_port(modem): return True + def mock_detect_usable_serial_port(modem, stop_on_first=True): - return '/dev/ttyUSB0' + return "/dev/ttyUSB0" + @pytest.fixture def no_serial_port(monkeypatch): - monkeypatch.setattr(BG96, '_read_from_serial_port', mock_read) - monkeypatch.setattr(BG96, '_readline_from_serial_port', mock_readline) - monkeypatch.setattr(BG96, '_write_to_serial_port_and_flush', mock_write) - monkeypatch.setattr(BG96, 'openSerialPort', mock_open_serial_port) - monkeypatch.setattr(BG96, 'closeSerialPort', mock_close_serial_port) - monkeypatch.setattr(BG96, 'detect_usable_serial_port', mock_detect_usable_serial_port) + monkeypatch.setattr(BG96, "_read_from_serial_port", mock_read) + monkeypatch.setattr(BG96, "_readline_from_serial_port", mock_readline) + monkeypatch.setattr(BG96, "_write_to_serial_port_and_flush", mock_write) + monkeypatch.setattr(BG96, "openSerialPort", mock_open_serial_port) + monkeypatch.setattr(BG96, "closeSerialPort", mock_close_serial_port) + monkeypatch.setattr(BG96, "detect_usable_serial_port", mock_detect_usable_serial_port) + def test_init_BG96_no_args(no_serial_port): modem = BG96() - assert(modem.timeout == 1) - assert(modem.socket_identifier == 0) - assert(modem.chatscript_file.endswith('/chatscripts/default-script')) - assert(modem._at_sockets_available) - -@patch.object(BG96, 'set') -@patch.object(BG96, 'command') -@patch.object(BG96, '_is_pdp_context_active') + assert modem.timeout == 1 + assert modem.socket_identifier == 0 + assert modem.chatscript_file.endswith("/chatscripts/default-script") + assert modem._at_sockets_available + + +@patch.object(BG96, "set") +@patch.object(BG96, "command") +@patch.object(BG96, "_is_pdp_context_active") def test_close_socket(mock_pdp, mock_command, mock_set, no_serial_port): modem = BG96() modem.socket_identifier = 1 @@ -61,6 +70,5 @@ def test_close_socket(mock_pdp, mock_command, mock_set, no_serial_port): mock_command.return_value = (ModemResult.OK, None) mock_pdp.return_value = True modem.close_socket() - mock_set.assert_called_with('+QIACT', '0', timeout=30) - mock_command.assert_called_with('+QICLOSE', 1) - + mock_set.assert_called_with("+QIACT", "0", timeout=30) + mock_command.assert_called_with("+QICLOSE", 1) diff --git a/tests/Modem/test_EC21.py b/tests/Modem/test_EC21.py index b127e7a4..b36e24e0 100644 --- a/tests/Modem/test_EC21.py +++ b/tests/Modem/test_EC21.py @@ -17,43 +17,52 @@ sys.path.append("..") sys.path.append("../..") + def mock_write(modem, message): return True + def mock_read(modem): return True + def mock_readline(modem, timeout=None, hide=False): - return '' + return "" + def mock_open_serial_port(modem, device_name=None): return True + def mock_close_serial_port(modem): return True + def mock_detect_usable_serial_port(modem, stop_on_first=True): - return '/dev/ttyUSB0' + return "/dev/ttyUSB0" + @pytest.fixture def no_serial_port(monkeypatch): - monkeypatch.setattr(EC21, '_read_from_serial_port', mock_read) - monkeypatch.setattr(EC21, '_readline_from_serial_port', mock_readline) - monkeypatch.setattr(EC21, '_write_to_serial_port_and_flush', mock_write) - monkeypatch.setattr(EC21, 'openSerialPort', mock_open_serial_port) - monkeypatch.setattr(EC21, 'closeSerialPort', mock_close_serial_port) - monkeypatch.setattr(EC21, 'detect_usable_serial_port', mock_detect_usable_serial_port) + monkeypatch.setattr(EC21, "_read_from_serial_port", mock_read) + monkeypatch.setattr(EC21, "_readline_from_serial_port", mock_readline) + monkeypatch.setattr(EC21, "_write_to_serial_port_and_flush", mock_write) + monkeypatch.setattr(EC21, "openSerialPort", mock_open_serial_port) + monkeypatch.setattr(EC21, "closeSerialPort", mock_close_serial_port) + monkeypatch.setattr(EC21, "detect_usable_serial_port", mock_detect_usable_serial_port) + def test_init_EC21_no_args(no_serial_port): modem = EC21() - assert(modem.timeout == 1) - assert(modem.socket_identifier == 0) - assert(modem.chatscript_file.endswith('/chatscripts/default-script')) - assert(modem._at_sockets_available) - -@patch.object(EC21, 'set') -@patch.object(EC21, 'command') -@patch.object(EC21, '_is_pdp_context_active') + assert modem.timeout == 1 + assert modem.socket_identifier == 0 + assert modem.chatscript_file.endswith("/chatscripts/default-script") + assert modem._at_sockets_available + + +@patch.object(EC21, "set") +@patch.object(EC21, "command") +@patch.object(EC21, "_is_pdp_context_active") def test_close_socket(mock_pdp, mock_command, mock_set, no_serial_port): modem = EC21() modem.socket_identifier = 1 @@ -61,5 +70,5 @@ def test_close_socket(mock_pdp, mock_command, mock_set, no_serial_port): mock_command.return_value = (ModemResult.OK, None) mock_pdp.return_value = True modem.close_socket() - mock_set.assert_called_with('+QIDEACT', '1', timeout=30) - mock_command.assert_called_with('+QICLOSE', 1) + mock_set.assert_called_with("+QIDEACT", "1", timeout=30) + mock_command.assert_called_with("+QICLOSE", 1) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 9aafd310..ae4d7456 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -6,7 +6,7 @@ # # test_Quectel.py - This file implements unit tests for the Quectel modem interface. -from unittest.mock import patch +from unittest.mock import patch, call import pytest import sys @@ -17,64 +17,84 @@ sys.path.append("..") sys.path.append("../..") + def mock_write(modem, message): return True + def mock_read(modem): return True + def mock_readline(modem, timeout=None, hide=False): - return '' + return "" + def mock_open_serial_port(modem, device_name=None): return True + def mock_close_serial_port(modem): return True + def mock_detect_usable_serial_port(modem, stop_on_first=True): - return '/dev/ttyUSB0' + return "/dev/ttyUSB0" + @pytest.fixture def no_serial_port(monkeypatch): - monkeypatch.setattr(Quectel, '_read_from_serial_port', mock_read) - monkeypatch.setattr(Quectel, '_readline_from_serial_port', mock_readline) - monkeypatch.setattr(Quectel, '_write_to_serial_port_and_flush', mock_write) - monkeypatch.setattr(Quectel, 'openSerialPort', mock_open_serial_port) - monkeypatch.setattr(Quectel, 'closeSerialPort', mock_close_serial_port) - monkeypatch.setattr(Quectel, 'detect_usable_serial_port', mock_detect_usable_serial_port) + monkeypatch.setattr(Quectel, "_read_from_serial_port", mock_read) + monkeypatch.setattr(Quectel, "_readline_from_serial_port", mock_readline) + monkeypatch.setattr(Quectel, "_write_to_serial_port_and_flush", mock_write) + monkeypatch.setattr(Quectel, "openSerialPort", mock_open_serial_port) + monkeypatch.setattr(Quectel, "closeSerialPort", mock_close_serial_port) + monkeypatch.setattr(Quectel, "detect_usable_serial_port", mock_detect_usable_serial_port) + def test_init_Quectel_no_args(no_serial_port): modem = Quectel() - assert(modem.timeout == 1) - assert(modem.socket_identifier == 0) - assert(modem.chatscript_file.endswith('/chatscripts/default-script')) - assert(modem._at_sockets_available) + assert modem.timeout == 1 + assert modem.socket_identifier == 0 + assert modem.chatscript_file.endswith("/chatscripts/default-script") + assert modem._at_sockets_available + -@patch.object(Quectel, 'command') +@patch.object(Quectel, "command") def test_connect_socket(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 - host = 'hologram.io' + host = "hologram.io" port = 9999 modem.connect_socket(host, port) - mock_command.assert_called_with('+QIOPEN', '1,0,\"TCP\",\"%s\",%d,0,1' % (host, port)) + mock_command.assert_called_with("+QIOPEN", '1,0,"TCP","%s",%d,0,1' % (host, port)) -@patch.object(Quectel, 'set') + +@patch.object(Quectel, "set") def test_write_socket_small(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 - data = b'Message smaller than 510 bytes' + data = b"Message smaller than 510 bytes" mock_command.return_value = (ModemResult.OK, None) modem.write_socket(data) - mock_command.assert_called_with('+QISENDEX', '1,"4d65737361676520736d616c6c6572207468616e20353130206279746573"', timeout=10) + mock_command.assert_called_with( + "+QISENDEX", + '1,"4d65737361676520736d616c6c6572207468616e20353130206279746573"', + timeout=10, + ) + -@patch.object(Quectel, 'set') +@patch.object(Quectel, "set") def test_write_socket_large(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 - data = b'a'*300 + data = b"a" * 300 mock_command.return_value = (ModemResult.OK, None) modem.write_socket(data) - mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*255), timeout=10) - mock_command.assert_called_with('+QISENDEX', '1,\"%s\"' % ('61'*45), timeout=10) \ No newline at end of file + mock_command.assert_has_calls( + [ + call("+QISENDEX", '1,"%s"' % ("61" * 255), timeout=10), + call("+QISENDEX", '1,"%s"' % ("61" * 45), timeout=10), + ], + any_order=True, + ) From c56fb9c582534d3ea644fb7e056061b3352caee7 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 15:26:08 -0600 Subject: [PATCH 19/26] serial init universal, add more quectel tests --- Hologram/Network/Modem/E303.py | 10 +------ Hologram/Network/Modem/E372.py | 10 +------ Hologram/Network/Modem/MS2131.py | 10 +------ Hologram/Network/Modem/Modem.py | 8 ++++- Hologram/Network/Modem/Nova_U201.py | 4 --- Hologram/Network/Modem/Quectel.py | 10 ------- tests/Modem/test_Quectel.py | 46 ++++++++++++++++++++++++++++- 7 files changed, 55 insertions(+), 43 deletions(-) diff --git a/Hologram/Network/Modem/E303.py b/Hologram/Network/Modem/E303.py index 2f066e9f..48e7f458 100644 --- a/Hologram/Network/Modem/E303.py +++ b/Hologram/Network/Modem/E303.py @@ -25,15 +25,7 @@ def __init__(self, device_name=None, baud_rate='9600', def connect(self, timeout = DEFAULT_E303_TIMEOUT): return super().connect(timeout) - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.command("+CTZU", "1") #time/zone sync - self.command("+CTZR", "1") #time/zone URC - #self.command("+CPIN", "") #set SIM PIN - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() + def set_network_registration_status(self): self.command("+CREG", "2") self.command("+CGREG", "2") diff --git a/Hologram/Network/Modem/E372.py b/Hologram/Network/Modem/E372.py index cfed7a31..195df12d 100644 --- a/Hologram/Network/Modem/E372.py +++ b/Hologram/Network/Modem/E372.py @@ -25,15 +25,7 @@ def __init__(self, device_name=None, baud_rate='9600', def connect(self, timeout = DEFAULT_E372_TIMEOUT): return super().connect(timeout) - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.command("+CTZU", "1") #time/zone sync - self.command("+CTZR", "1") #time/zone URC - #self.command("+CPIN", "") #set SIM PIN - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() + def set_network_registration_status(self): self.command("+CREG", "2") self.command("+CGREG", "2") diff --git a/Hologram/Network/Modem/MS2131.py b/Hologram/Network/Modem/MS2131.py index ecc80e28..a738f314 100644 --- a/Hologram/Network/Modem/MS2131.py +++ b/Hologram/Network/Modem/MS2131.py @@ -26,15 +26,7 @@ def __init__(self, device_name=None, baud_rate='9600', def connect(self, timeout = DEFAULT_MS2131_TIMEOUT): return super().connect(timeout) - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.command("+CTZU", "1") #time/zone sync - self.command("+CTZR", "1") #time/zone URC - #self.command("+CPIN", "") #set SIM PIN - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() + def set_network_registration_status(self): self.command("+CREG", "2") self.command("+CGREG", "2") diff --git a/Hologram/Network/Modem/Modem.py b/Hologram/Network/Modem/Modem.py index c55468cc..ca5222d4 100644 --- a/Hologram/Network/Modem/Modem.py +++ b/Hologram/Network/Modem/Modem.py @@ -226,7 +226,13 @@ def initialize_serial_interface(self): self.init_serial_commands() def init_serial_commands(self): - pass + self.command("E0") #echo off + self.command("+CMEE", "2") #set verbose error codes + self.command("+CPIN?") + self.set_timezone_configs() + self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") + self.set_sms_configs() + self.set_network_registration_status() def set_sms_configs(self): self.command("+CMGF", "0") #SMS PDU format diff --git a/Hologram/Network/Modem/Nova_U201.py b/Hologram/Network/Modem/Nova_U201.py index d3111b80..d6a23d3c 100644 --- a/Hologram/Network/Modem/Nova_U201.py +++ b/Hologram/Network/Modem/Nova_U201.py @@ -71,10 +71,6 @@ def enforce_nova_modem_mode(self): self.device_name = devices[0] super().initialize_serial_interface() - def init_serial_commands(self): - super().init_serial_commands() - self.set_timezone_configs() - def set_network_registration_status(self): self.command("+CREG", "2") self.command("+CGREG", "2") diff --git a/Hologram/Network/Modem/Quectel.py b/Hologram/Network/Modem/Quectel.py index be99d11c..d996dd03 100644 --- a/Hologram/Network/Modem/Quectel.py +++ b/Hologram/Network/Modem/Quectel.py @@ -145,16 +145,6 @@ def _is_pdp_context_active(self): self.logger.error(repr(e)) return False - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.set_timezone_configs() - #self.command("+CPIN", "") #set SIM PIN - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() - self.set_network_registration_status() - def set_network_registration_status(self): self.command("+CREG", "2") self.command("+CEREG", "2") diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index ae4d7456..97dfef91 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -11,6 +11,7 @@ import sys from Hologram.Network.Modem.Quectel import Quectel +from Hologram.Network.Modem.Modem import Modem from UtilClasses import ModemResult sys.path.append(".") @@ -27,7 +28,7 @@ def mock_read(modem): def mock_readline(modem, timeout=None, hide=False): - return "" + return "From Serial" def mock_open_serial_port(modem, device_name=None): @@ -59,6 +60,20 @@ def test_init_Quectel_no_args(no_serial_port): assert modem.chatscript_file.endswith("/chatscripts/default-script") assert modem._at_sockets_available +@patch.object(Quectel, "check_registered") +@patch.object(Quectel, "set") +@patch.object(Quectel, "command") +def test_create_socket(mock_command, mock_set, mock_check, no_serial_port): + modem = Quectel() + modem.apn = 'test' + mock_check.return_value = True + # The PDP context is not active + mock_command.return_value = (ModemResult.OK, '+QIACT: 0,0') + mock_set.return_value = (ModemResult.OK, None) + modem.create_socket() + mock_command.assert_called_with("+QIACT?") + mock_set.assert_called_with("+QICSGP", '1,1,\"test\",\"\",\"\",1') + mock_set.assert_called_with("+QIACT", '1', timeout=30) @patch.object(Quectel, "command") def test_connect_socket(mock_command, no_serial_port): @@ -98,3 +113,32 @@ def test_write_socket_large(mock_command, no_serial_port): ], any_order=True, ) + +@patch.object(Quectel, "set") +def test_read_socket(mock_command, no_serial_port): + modem = Quectel() + modem.socket_identifier = 1 + mock_command.return_value = (ModemResult.OK, b'+QIRD: "Some val"') + # Double quotes should be stripped from the reutrn value + assert (modem.read_socket(payload_length=10) == 'Some val') + mock_command.assert_called_with("+QIRD", '1,10') + +def test_handle_open_urc(no_serial_port): + modem = Quectel() + modem.handleURC('+QIOPEN: 1,0') + assert modem.urc_state == Modem.SOCKET_WRITE_STATE + assert modem.socket_identifier == 1 + +def test_handle_received_data_urc(no_serial_port): + modem = Quectel() + modem.handleURC('+QIURC: \"recv\",1,25') + assert modem.urc_state == Modem.SOCKET_SEND_READ + assert modem.socket_identifier == 1 + assert modem.last_read_payload_length == 25 + assert modem.urc_response == "From Serial" + +def test_handle_socket_closed_urc(no_serial_port): + modem = Quectel() + modem.handleURC('+QIURC: \"closed\",1') + assert modem.urc_state == Modem.SOCKET_CLOSED + assert modem.socket_identifier == 1 \ No newline at end of file From 843cf53d379785551d5c69bc91ee7f6c9ba1a6d8 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 16:28:04 -0600 Subject: [PATCH 20/26] stop the indefinite test... --- tests/Modem/test_Quectel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 97dfef91..869daafa 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -141,4 +141,5 @@ def test_handle_socket_closed_urc(no_serial_port): modem = Quectel() modem.handleURC('+QIURC: \"closed\",1') assert modem.urc_state == Modem.SOCKET_CLOSED - assert modem.socket_identifier == 1 \ No newline at end of file + assert modem.socket_identifier == 1 + From ddd97d708290288d5af17cd04edb473e86783b0b Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 19:00:37 -0600 Subject: [PATCH 21/26] skip urc tests might have infinite loop --- tests/Modem/test_Quectel.py | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 869daafa..8a22aa3b 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -123,23 +123,23 @@ def test_read_socket(mock_command, no_serial_port): assert (modem.read_socket(payload_length=10) == 'Some val') mock_command.assert_called_with("+QIRD", '1,10') -def test_handle_open_urc(no_serial_port): - modem = Quectel() - modem.handleURC('+QIOPEN: 1,0') - assert modem.urc_state == Modem.SOCKET_WRITE_STATE - assert modem.socket_identifier == 1 - -def test_handle_received_data_urc(no_serial_port): - modem = Quectel() - modem.handleURC('+QIURC: \"recv\",1,25') - assert modem.urc_state == Modem.SOCKET_SEND_READ - assert modem.socket_identifier == 1 - assert modem.last_read_payload_length == 25 - assert modem.urc_response == "From Serial" - -def test_handle_socket_closed_urc(no_serial_port): - modem = Quectel() - modem.handleURC('+QIURC: \"closed\",1') - assert modem.urc_state == Modem.SOCKET_CLOSED - assert modem.socket_identifier == 1 +# def test_handle_open_urc(no_serial_port): +# modem = Quectel() +# modem.handleURC('+QIOPEN: 1,0') +# assert modem.urc_state == Modem.SOCKET_WRITE_STATE +# assert modem.socket_identifier == 1 + +# def test_handle_received_data_urc(no_serial_port): +# modem = Quectel() +# modem.handleURC('+QIURC: \"recv\",1,25') +# assert modem.urc_state == Modem.SOCKET_SEND_READ +# assert modem.socket_identifier == 1 +# assert modem.last_read_payload_length == 25 +# assert modem.urc_response == "From Serial" + +# def test_handle_socket_closed_urc(no_serial_port): +# modem = Quectel() +# modem.handleURC('+QIURC: \"closed\",1') +# assert modem.urc_state == Modem.SOCKET_CLOSED +# assert modem.socket_identifier == 1 From 975ef339db3864e1563066c189256e68faa8c482 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 19:04:35 -0600 Subject: [PATCH 22/26] revert readline change --- tests/Modem/test_Quectel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 8a22aa3b..0610a327 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -28,7 +28,7 @@ def mock_read(modem): def mock_readline(modem, timeout=None, hide=False): - return "From Serial" + return "" def mock_open_serial_port(modem, device_name=None): From 8266071ce4c6550dcceb15c64278294bf1837317 Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 19:08:36 -0600 Subject: [PATCH 23/26] fix failing tests --- tests/Modem/test_Quectel.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 0610a327..1da78822 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -72,8 +72,13 @@ def test_create_socket(mock_command, mock_set, mock_check, no_serial_port): mock_set.return_value = (ModemResult.OK, None) modem.create_socket() mock_command.assert_called_with("+QIACT?") - mock_set.assert_called_with("+QICSGP", '1,1,\"test\",\"\",\"\",1') - mock_set.assert_called_with("+QIACT", '1', timeout=30) + mock_set.assert_has_calls( + [ + call("+QICSGP", '1,1,\"test\",\"\",\"\",1'), + call("+QIACT", '1', timeout=30) + ], + any_order=True + ) @patch.object(Quectel, "command") def test_connect_socket(mock_command, no_serial_port): @@ -118,7 +123,7 @@ def test_write_socket_large(mock_command, no_serial_port): def test_read_socket(mock_command, no_serial_port): modem = Quectel() modem.socket_identifier = 1 - mock_command.return_value = (ModemResult.OK, b'+QIRD: "Some val"') + mock_command.return_value = (ModemResult.OK, '+QIRD: "Some val"') # Double quotes should be stripped from the reutrn value assert (modem.read_socket(payload_length=10) == 'Some val') mock_command.assert_called_with("+QIRD", '1,10') From b3568675a8f6d99528f9871396b47dfb36ac5a1a Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 19:13:18 -0600 Subject: [PATCH 24/26] mock serial init for modem --- tests/Modem/test_Modem.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Modem/test_Modem.py b/tests/Modem/test_Modem.py index cbf71f40..e290d148 100644 --- a/tests/Modem/test_Modem.py +++ b/tests/Modem/test_Modem.py @@ -23,6 +23,9 @@ def mock_write(modem, message): def mock_read(modem): return True +def mock_init_commands(modem): + return True + def mock_readline(modem, timeout=None, hide=False): return '' @@ -49,6 +52,7 @@ def no_serial_port(monkeypatch): monkeypatch.setattr(Modem, '_read_from_serial_port', mock_read) monkeypatch.setattr(Modem, '_readline_from_serial_port', mock_readline) monkeypatch.setattr(Modem, '_write_to_serial_port_and_flush', mock_write) + monkeypatch.setattr(Modem, 'init_serial_commands', mock_init_commands) monkeypatch.setattr(Modem, 'openSerialPort', mock_open_serial_port) monkeypatch.setattr(Modem, 'closeSerialPort', mock_close_serial_port) monkeypatch.setattr(Modem, 'detect_usable_serial_port', mock_detect_usable_serial_port) From 03691256d8cf5e81accaf0b46a2e295850767e3b Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 19:17:32 -0600 Subject: [PATCH 25/26] uncomment urc tests --- tests/Modem/test_Quectel.py | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/Modem/test_Quectel.py b/tests/Modem/test_Quectel.py index 1da78822..d7e956dd 100644 --- a/tests/Modem/test_Quectel.py +++ b/tests/Modem/test_Quectel.py @@ -128,23 +128,23 @@ def test_read_socket(mock_command, no_serial_port): assert (modem.read_socket(payload_length=10) == 'Some val') mock_command.assert_called_with("+QIRD", '1,10') -# def test_handle_open_urc(no_serial_port): -# modem = Quectel() -# modem.handleURC('+QIOPEN: 1,0') -# assert modem.urc_state == Modem.SOCKET_WRITE_STATE -# assert modem.socket_identifier == 1 - -# def test_handle_received_data_urc(no_serial_port): -# modem = Quectel() -# modem.handleURC('+QIURC: \"recv\",1,25') -# assert modem.urc_state == Modem.SOCKET_SEND_READ -# assert modem.socket_identifier == 1 -# assert modem.last_read_payload_length == 25 -# assert modem.urc_response == "From Serial" - -# def test_handle_socket_closed_urc(no_serial_port): -# modem = Quectel() -# modem.handleURC('+QIURC: \"closed\",1') -# assert modem.urc_state == Modem.SOCKET_CLOSED -# assert modem.socket_identifier == 1 +def test_handle_open_urc(no_serial_port): + modem = Quectel() + modem.handleURC('+QIOPEN: 1,0') + assert modem.urc_state == Modem.SOCKET_WRITE_STATE + assert modem.socket_identifier == 1 + +def test_handle_received_data_urc(no_serial_port): + modem = Quectel() + modem.handleURC('+QIURC: \"recv\",1,25') + assert modem.urc_state == Modem.SOCKET_SEND_READ + assert modem.socket_identifier == 1 + assert modem.last_read_payload_length == 25 + assert modem.urc_response == "" + +def test_handle_socket_closed_urc(no_serial_port): + modem = Quectel() + modem.handleURC('+QIURC: \"closed\",1') + assert modem.urc_state == Modem.SOCKET_CLOSED + assert modem.socket_identifier == 1 From cd9c71750f60f5440141019755e4b5847863375c Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Tue, 15 Nov 2022 19:28:59 -0600 Subject: [PATCH 26/26] remove duped code from nova class --- Hologram/Network/Modem/Nova.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Hologram/Network/Modem/Nova.py b/Hologram/Network/Modem/Nova.py index 8ed3227f..c3ddd031 100644 --- a/Hologram/Network/Modem/Nova.py +++ b/Hologram/Network/Modem/Nova.py @@ -27,14 +27,6 @@ def disable_at_sockets_mode(self): def enable_at_sockets_mode(self): self._at_sockets_available = True - def init_serial_commands(self): - self.command("E0") #echo off - self.command("+CMEE", "2") #set verbose error codes - self.command("+CPIN?") - self.command("+CPMS", "\"ME\",\"ME\",\"ME\"") - self.set_sms_configs() - self.set_network_registration_status() - @property def version(self): return self._basic_command('I9')