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'
0 commit comments