Skip to content

Commit 653481d

Browse files
authored
Add BG96 Support (#37)
* add bg96 to modems * update description and enable at sockets * Update BG96.py * check if active * send the right permutation * decode and use command * wait for socket to open and handle other actions * better urc handling * more logging * handle attribute errors too * handle urc types better * better socket state handling * bg96 is send ok not ok * log more info * use set instead of command * add send ok as possible results * handle send ok in process response * close properly and update read_socket * handle recv response from urc * fix missing imports * clean up logging a bit * remove another logging instance * fix merge conflict * fix copy pasta
1 parent 7c45b17 commit 653481d

File tree

3 files changed

+194
-1
lines changed

3 files changed

+194
-1
lines changed

Hologram/Network/Cellular.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from Hologram.Event import Event
1212
from Exceptions.HologramError import NetworkError
1313
from Hologram.Network.Route import Route
14-
from Hologram.Network.Modem import Modem, E303, MS2131, E372, Nova_U201, NovaM, DriverLoader
14+
from Hologram.Network.Modem import Modem, E303, MS2131, E372, BG96, Nova_U201, NovaM, DriverLoader
1515
from Hologram.Network import Network, NetworkScope
1616
import time
1717
from serial.tools import list_ports
@@ -31,6 +31,7 @@ class Cellular(Network):
3131
'e303': E303.E303,
3232
'ms2131': MS2131.MS2131,
3333
'e372': E372.E372,
34+
'bg96': BG96.BG96,
3435
'nova': Nova_U201.Nova_U201,
3536
'novam': NovaM.NovaM,
3637
'': Modem

Hologram/Network/Modem/BG96.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# BG96.py - Hologram Python SDK Quectel BG96 modem interface
2+
#
3+
# Author: Hologram <[email protected]>
4+
#
5+
# Copyright 2016 - Hologram (Konekt, Inc.)
6+
#
7+
#
8+
# LICENSE: Distributed under the terms of the MIT License
9+
#
10+
import binascii
11+
import time
12+
13+
from serial.serialutil import Timeout
14+
15+
from Hologram.Network.Modem import Modem
16+
from Hologram.Event import Event
17+
from UtilClasses import ModemResult
18+
from Exceptions.HologramError import SerialError, NetworkError
19+
20+
DEFAULT_BG96_TIMEOUT = 200
21+
22+
class BG96(Modem):
23+
usb_ids = [('2c7c', '0296')]
24+
25+
def __init__(self, device_name=None, baud_rate='9600',
26+
chatscript_file=None, event=Event()):
27+
28+
super().__init__(device_name=device_name, baud_rate=baud_rate,
29+
chatscript_file=chatscript_file, event=event)
30+
self._at_sockets_available = True
31+
self.urc_response = ''
32+
33+
def connect(self, timeout=DEFAULT_BG96_TIMEOUT):
34+
35+
success = super().connect(timeout)
36+
37+
# put serial mode on other port
38+
# if success is True:
39+
# # detect another open serial port to use for PPP
40+
# devices = self.detect_usable_serial_port()
41+
# if not devices:
42+
# raise SerialError('Not enough serial ports detected for Nova')
43+
# self.logger.debug('Moving connection to port %s', devices[0])
44+
# self.device_name = devices[0]
45+
# super().initialize_serial_interface()
46+
47+
return success
48+
49+
def send_message(self, data, timeout=Modem.DEFAULT_SEND_TIMEOUT):
50+
# Waiting for the open socket urc
51+
while self.urc_state != Modem.SOCKET_WRITE_STATE:
52+
self.checkURC()
53+
54+
self.write_socket(data)
55+
56+
loop_timeout = Timeout(timeout)
57+
while self.urc_state != Modem.SOCKET_SEND_READ:
58+
self.checkURC()
59+
if self.urc_state != Modem.SOCKET_SEND_READ:
60+
if loop_timeout.expired():
61+
raise SerialError('Timeout occurred waiting for message status')
62+
time.sleep(self._RETRY_DELAY)
63+
elif self.urc_state == Modem.SOCKET_CLOSED:
64+
return '[1,0]' #this is connection closed for hologram cloud response
65+
66+
return self.urc_response
67+
68+
def create_socket(self):
69+
self._set_up_pdp_context()
70+
71+
def connect_socket(self, host, port):
72+
self.command('+QIOPEN', '1,0,\"TCP\",\"%s\",%d,0,1' % (host, port))
73+
# According to the BG96 Docs
74+
# Have to wait for URC response “+QIOPEN: <connectID>,<err>”
75+
76+
def close_socket(self, socket_identifier=None):
77+
ok, _ = self.command('+QICLOSE', self.socket_identifier)
78+
if ok != ModemResult.OK:
79+
self.logger.error('Failed to close socket')
80+
self.urc_state = Modem.SOCKET_CLOSED
81+
82+
def write_socket(self, data):
83+
hexdata = binascii.hexlify(data)
84+
# We have to do it in chunks of 510 since 512 is actually too long (CMEE error)
85+
# and we need 2n chars for hexified data
86+
for chunk in self._chunks(hexdata, 510):
87+
value = '%d,\"%s\"' % (self.socket_identifier, chunk.decode())
88+
ok, _ = self.set('+QISENDEX', value, timeout=10)
89+
if ok != ModemResult.OK:
90+
self.logger.error('Failed to write to socket')
91+
raise NetworkError('Failed to write to socket')
92+
93+
def read_socket(self, socket_identifier=None, payload_length=None):
94+
95+
if socket_identifier is None:
96+
socket_identifier = self.socket_identifier
97+
98+
if payload_length is None:
99+
payload_length = self.last_read_payload_length
100+
101+
ok, resp = self.set('+QIRD', '%d,%d' % (socket_identifier, payload_length))
102+
if ok == ModemResult.OK:
103+
resp = resp.lstrip('+QIRD: ')
104+
if resp is not None:
105+
resp = resp.strip('"')
106+
try:
107+
resp = resp.decode()
108+
except:
109+
# This is some sort of binary data that can't be decoded so just
110+
# return the bytes. We might want to make this happen via parameter
111+
# in the future so it is more deterministic
112+
self.logger.debug('Could not decode recieved data')
113+
114+
return resp
115+
116+
def is_registered(self):
117+
return self.check_registered('+CREG') or self.check_registered('+CGREG')
118+
119+
# EFFECTS: Handles URC related AT command responses.
120+
def handleURC(self, urc):
121+
if urc.startswith('+QIOPEN: '):
122+
response_list = urc.lstrip('+QIOPEN: ').split(',')
123+
socket_identifier = int(response_list[0])
124+
err = int(response_list[-1])
125+
if err == 0:
126+
self.urc_state = Modem.SOCKET_WRITE_STATE
127+
self.socket_identifier = socket_identifier
128+
else:
129+
self.logger.error('Failed to open socket')
130+
raise NetworkError('Failed to open socket')
131+
return
132+
if urc.startswith('+QIURC: '):
133+
response_list = urc.lstrip('+QIURC: ').split(',')
134+
urctype = response_list[0]
135+
if urctype == '\"recv\"':
136+
self.urc_state = Modem.SOCKET_SEND_READ
137+
self.socket_identifier = int(response_list[1])
138+
self.last_read_payload_length = int(response_list[2])
139+
self.urc_response = self._readline_from_serial_port(5)
140+
if urctype == '\"closed\"':
141+
self.urc_state = Modem.SOCKET_CLOSED
142+
self.socket_identifier = int(response_list[-1])
143+
return
144+
super().handleURC(urc)
145+
146+
def _is_pdp_context_active(self):
147+
if not self.is_registered():
148+
return False
149+
150+
ok, r = self.command('+QIACT?')
151+
if ok == ModemResult.OK:
152+
try:
153+
pdpstatus = int(r.lstrip('+QIACT: ').split(',')[1])
154+
# 1: PDP active
155+
return pdpstatus == 1
156+
except (IndexError, ValueError) as e:
157+
self.logger.error(repr(e))
158+
except AttributeError as e:
159+
self.logger.error(repr(e))
160+
return False
161+
162+
def init_serial_commands(self):
163+
self.command("E0") #echo off
164+
self.command("+CMEE", "2") #set verbose error codes
165+
self.command("+CPIN?")
166+
self.set_timezone_configs()
167+
#self.command("+CPIN", "") #set SIM PIN
168+
self.command("+CPMS", "\"ME\",\"ME\",\"ME\"")
169+
self.set_sms_configs()
170+
self.set_network_registration_status()
171+
172+
def set_network_registration_status(self):
173+
self.command("+CREG", "2")
174+
self.command("+CGREG", "2")
175+
176+
def _set_up_pdp_context(self):
177+
if self._is_pdp_context_active(): return True
178+
self.logger.info('Setting up PDP context')
179+
self.set('+QICSGP', '1,1,\"hologram\",\"\",\"\",1')
180+
ok, _ = self.set('+QIACT', '1', timeout=30)
181+
if ok != ModemResult.OK:
182+
self.logger.error('PDP Context setup failed')
183+
raise NetworkError('Failed PDP context setup')
184+
else:
185+
self.logger.info('PDP context active')
186+
187+
@property
188+
def description(self):
189+
return 'Quecetel BG96'

Hologram/Network/Modem/Modem.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,9 @@ def process_response(self, cmd, timeout=None, hide=False):
464464
if response == 'OK':
465465
return ModemResult.OK
466466

467+
if response == 'SEND OK':
468+
return ModemResult.OK
469+
467470
if response.startswith('+'):
468471
if response.lower().startswith(cmd.lower() + ': '):
469472
self.response.append(response)

0 commit comments

Comments
 (0)