Skip to content

move ublox specific commands and URCs to base class #40

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 20 additions & 151 deletions Hologram/Network/Modem/Modem.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@


from collections import deque
import binascii
import datetime
import logging
import os
import serial
from serial.tools import list_ports
Expand All @@ -34,7 +32,7 @@ class Modem(IModem):
DEFAULT_SERIAL_READ_SIZE = 256
DEFAULT_SERIAL_TIMEOUT = 1
DEFAULT_SERIAL_RETRIES = 0
DEFAULT_SEND_TIMEOUT = 10
DEFAULT_SEND_TIMEOUT = 20

_RETRY_DELAY = 0.05 # 50 millisecond delay to avoid spinning loops

Expand Down Expand Up @@ -245,9 +243,6 @@ def set_timezone_configs(self):
def set_network_registration_status(self):
pass

def reset(self):
self.set('+CFUN', '16') # restart the modem

def radio_power(self, power_mode):
cfun_val = '1' if power_mode else '0'
ok, r = self.command('+CFUN', cfun_val, timeout=5)
Expand Down Expand Up @@ -289,82 +284,29 @@ def _read_and_append_message_receive_buffer(self, socket_identifier, payload_len
self.close_socket(socket_identifier=socket_identifier)

def create_socket(self):
op = self._basic_set('+USOCR', '6', strip_val=False)
if op is not None:
self.socket_identifier = int(op)
raise NotImplementedError("Modem does not have an AT Socket Mode")

# REQUIRES: The host and port.
# EFFECTS: Issues an AT command to connect to the specified socket identifier.
def connect_socket(self, host, port):
at_command_val = "%d,\"%s\",%s" % (self.socket_identifier, host, port)
ok, _ = self.set('+USOCO', at_command_val, timeout=20)
if ok != ModemResult.OK:
self.logger.error('Failed to connect socket')
raise NetworkError('Failed to connect socket')
else:
self.logger.info('Connect socket is successful')
raise NotImplementedError("Modem does not have an AT Socket Mode")

def listen_socket(self, port):
at_command_val = "%d,%s" % (self.socket_identifier, port)
self.listen_socket_identifier = self.socket_identifier
ok, _ = self.set('+USOLI', at_command_val, timeout=5)
if ok != ModemResult.OK:
self.logger.error('Failed to listen socket')
raise NetworkError('Failed to listen socket')
raise NotImplementedError("Modem does not have an AT Socket Mode")

def write_socket(self, data):
self.enable_hex_mode()
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 = b'%d,%d,\"%s\"' % (self.socket_identifier,
len(binascii.unhexlify(chunk)),
chunk)
ok, _ = self.set('+USOWR', value, timeout=10)
if ok != ModemResult.OK:
self.logger.error('Failed to write to socket')
raise NetworkError('Failed to write socket')
self.disable_hex_mode()
raise NotImplementedError("Modem does not have an AT Socket Mode")

def _chunks(self, data, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(data), n):
yield data[i:i + n]

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

self.enable_hex_mode()

resp = self._basic_set('+USORD', '%d,%d' % (socket_identifier, payload_length))
if resp is not None:
resp = resp.strip('"')
bytedata = binascii.unhexlify(resp)
try:
resp = bytedata.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
resp = bytedata

self.disable_hex_mode()
return resp
raise NotImplementedError("Modem does not have an AT Socket Mode")

def close_socket(self, socket_identifier=None):

if socket_identifier is None:
socket_identifier = self.socket_identifier

ok, r = self.set('+USOCL', "%s" % socket_identifier)
if ok != ModemResult.OK:
self.logger.info('Failed to close socket')
raise NotImplementedError("Modem does not have an AT Socket Mode")

def debugwrite(self, x, hide=False):
if not hide:
Expand Down Expand Up @@ -400,46 +342,16 @@ def checkURC(self, hide=False):

# EFFECTS: Handles URC related AT command responses.
def handleURC(self, urc):
self.logger.debug("URC! %s", urc)
self.logger.debug("URC! %s", urc)
self.logger.debug("handleURC state: %d", self.urc_state)

next_urc_state = self.urc_state

if urc.startswith("+CMTI: "):
self._handle_sms_receive_urc(urc)
elif urc.startswith('+UULOC: '):
self._handle_location_urc(urc)
elif urc.startswith('+UUSORD: '):

# Strip UUSORD socket identifier + payload length from the URC event.
# Example: {+UUSORD: 0,2} -> 0 and 2
response_list = urc.lstrip('+UUSORD: ').split(',')
socket_identifier = int(response_list[0])
payload_length = int(response_list[-1])

if self.urc_state == Modem.SOCKET_RECEIVE_READ:
self._read_and_append_message_receive_buffer(socket_identifier, payload_length)
else:
self.socket_identifier = socket_identifier
self.last_read_payload_length = payload_length
next_urc_state = Modem.SOCKET_SEND_READ
elif urc.startswith('+UUSOLI: '):
self._handle_listen_urc(urc)
self.last_read_payload_length = 0
next_urc_state = Modem.SOCKET_RECEIVE_READ
elif urc.startswith('+UUPSDD: '):
self.event.broadcast('cellular.forced_disconnect')
elif urc.startswith('+UUSOCL: '):
next_urc_state = Modem.SOCKET_CLOSED
else:
self.logger.debug("URC was not handled. \'%s\'", urc)

self.urc_state = next_urc_state


# URC handlers


def _handle_sms_receive_urc(self, urc):
self.event.broadcast('sms.received')

Expand Down Expand Up @@ -702,40 +614,13 @@ def is_registered(self):
pass

def _is_pdp_context_active(self):
if not self.is_registered():
return False

ok, r = self.set('+UPSND', '0,8')
if ok == ModemResult.OK:
try:
pdpstatus = int(r.lstrip('UPSND: ').split(',')[2])
# 1: PDP active
return pdpstatus == 1
except (IndexError, ValueError) as e:
self.logger.error(repr(e))
return False

def _set_up_pdp_context(self):
if self._is_pdp_context_active(): return True
self.logger.info('Setting up PDP context')
self.set('+UPSD', f'0,1,\"{self._apn}\"')
self.set('+UPSD', '0,7,\"0.0.0.0\"')
ok, _ = self.set('+UPSDA', '0,3', 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')
raise NotImplementedError('Modem PDP context setup not implemented')

def _tear_down_pdp_context(self):
if not self._is_pdp_context_active(): return True
self.logger.info('Tearing down PDP context')
ok, _ = self.set('+UPSDA', '0,4', timeout=30)
if ok != ModemResult.OK:
self.logger.error('PDP Context tear down failed')
else:
self.logger.info('PDP context deactivated')

raise NotImplementedError('Modem PDP context setup not implemented')

def __enforce_serial_port_open(self):
if not (self.serial_port and self.serial_port.isOpen()):
Expand Down Expand Up @@ -837,13 +722,10 @@ def enable_at_sockets_mode(self):
pass

def enable_hex_mode(self):
self.__set_hex_mode(1)
pass

def disable_hex_mode(self):
self.__set_hex_mode(0)

def __set_hex_mode(self, enable_hex_mode):
self.command('+UDCONF', '1,%d' % enable_hex_mode)
pass

@property
def serial_port(self):
Expand Down Expand Up @@ -895,27 +777,12 @@ def at_sockets_available(self):
return self._at_sockets_available

@property
def modem_mode(self):
mode_number = None
# trim:
# +UUSBCONF: 0,"",,"0x1102" -> 0
# +UUSBCONF: 2,"ECM",,"0x1104" -> 2
try:
ok, res = self.read('+UUSBCONF')
if ok == ModemResult.OK:
mode_number = int(res.lstrip('+UUSBCONF: ').split(',')[0])
except (IndexError, ValueError) as e:
self.logger.error(repr(e))
return mode_number
def modem_usb_mode(self):
raise NotImplementedError('This modem does not support this property')

@modem_mode.setter
def modem_mode(self, mode):
self.set('+UUSBCONF', str(mode))
self.logger.info('Restarting modem')
self.reset()
self.logger.info('Modem restarted')
self.closeSerialPort()
time.sleep(Modem.DEFAULT_MODEM_RESTART_TIME)
@modem_usb_mode.setter
def modem_usb_mode(self, mode):
raise NotImplementedError('This modem does not support this property')

@property
def localIPAddress(self):
Expand All @@ -933,7 +800,7 @@ def remoteIPAddress(self):

@property
def version(self):
raise NotImplementedError('This modem does not support this property')
return self._basic_command('I9')

@property
def imei(self):
Expand All @@ -947,3 +814,5 @@ def apn(self):
def apn(self, apn):
self._apn = apn
return self.set('+CGDCONT', f'1,"IP","{self._apn}"')


33 changes: 0 additions & 33 deletions Hologram/Network/Modem/Nova.py

This file was deleted.

4 changes: 2 additions & 2 deletions Hologram/Network/Modem/NovaM.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
# LICENSE: Distributed under the terms of the MIT License
#

from Hologram.Network.Modem.Nova import Nova
from Hologram.Network.Modem.UBlox import Ublox
from Hologram.Event import Event
from Exceptions.HologramError import NetworkError
from UtilClasses import ModemResult

DEFAULT_NOVAM_TIMEOUT = 200

class NovaM(Nova):
class NovaM(Ublox):

usb_ids = [('05c6', '90b2')]
module = 'option'
Expand Down
24 changes: 8 additions & 16 deletions Hologram/Network/Modem/Nova_U201.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
# LICENSE: Distributed under the terms of the MIT License
#

from Hologram.Network.Modem.Nova import Nova
from Hologram.Network.Modem.UBlox import Ublox
from Exceptions.HologramError import SerialError
from Hologram.Event import Event
from UtilClasses import Location
from UtilClasses import ModemResult

DEFAULT_NOVA_U201_TIMEOUT = 200

class Nova_U201(Nova):
class Nova_U201(Ublox):
usb_ids = [('1546','1102'),('1546','1104')]

def __init__(self, device_name=None, baud_rate='9600',
Expand All @@ -26,7 +26,7 @@ def __init__(self, device_name=None, baud_rate='9600',
chatscript_file=chatscript_file, event=event)
# We need to enforce multi serial port support. We then reinstantiate
# the serial interface with the correct device name.
self.enforce_nova_modem_mode()
self.enforce_nova_modem_usb_mode()
self._at_sockets_available = True
self.last_sim_otp_command_response = None
self.last_location = None
Expand Down Expand Up @@ -59,14 +59,14 @@ def is_registered(self):

# EFFECTS: Enforces that the Nova modem be in the correct mode to support multiple
# serial ports
def enforce_nova_modem_mode(self):
def enforce_nova_modem_usb_mode(self):

modem_mode = self.modem_mode
self.logger.debug('USB modem mode: ' + str(modem_mode))
modem_usb_mode = self.modem_usb_mode
self.logger.debug('USB modem mode: ' + str(modem_usb_mode))

# Set the modem mode to 0 if necessary.
if modem_mode == 2:
self.modem_mode = 0
if modem_usb_mode == 2:
self.modem_usb_mode = 0
devices = self.detect_usable_serial_port()
self.device_name = devices[0]
super().initialize_serial_interface()
Expand All @@ -89,14 +89,6 @@ def get_sim_otp_response(self, command):

return self.last_sim_otp_command_response

# EFFECTS: Handles URC related AT command responses.
def handleURC(self, urc):
if urc.startswith('+CSIM: '):
self.parse_and_populate_last_sim_otp_response(urc.lstrip('+CSIM: '))
return

super().handleURC(urc)

def populate_location_obj(self, response):
response_list = response.split(',')
self.last_location = Location(*response_list)
Expand Down
Loading