Skip to content

Commit 7a75b6a

Browse files
committed
Hologram Python SDK v0.7.0 release
1 parent f6c1fd2 commit 7a75b6a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1346
-1322
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ coverage.xml
4949
*.mo
5050
*.pot
5151

52+
# Jetbrains IDE stuff
53+
.idea
54+
5255
# Django stuff:
5356
*.log
5457
local_settings.py

ChangeLog

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
2017-10-24 Hologram <[email protected]>
2+
* Added modem is_connected interface. Checks for when a message/SMS can be sent.
3+
* Cleaned up example scripts.
4+
5+
2017-10-23 Hologram <[email protected]>
6+
* Added hologram network connect/disconnect CLI commands, deprecating
7+
modem connect/disconnect soon.
8+
* Writes/reads on AT command sockets are now done in hex mode.
9+
* Fix regression where a receive SMS CLI keyboard interrupt will print a
10+
stack trace.
11+
12+
2017-10-20 Hologram <[email protected]>
13+
* Sends/receives are done via AT sockets by default instead of a PPP session.
14+
115
2017-09-29 Hologram <[email protected]>
216
* Fixed bug where empty result list might cause an index error
317

Hologram/Cloud.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from Network import NetworkManager
1414
from Authentication import *
1515

16-
__version__ = '0.6.1'
16+
__version__ = '0.7.0'
1717

1818
class Cloud(object):
1919

Hologram/CustomCloud.py

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
MAX_QUEUED_CONNECTIONS = 5
2020
RECEIVE_TIMEOUT = 5
2121
SEND_TIMEOUT = 5
22-
MIN_PERIODIC_INTERVAL = 30
22+
MIN_PERIODIC_INTERVAL = 1
2323

2424
class CustomCloud(Cloud):
2525

@@ -54,11 +54,14 @@ def __init__(self, credentials, send_host='', send_port=0,
5454
if enable_inbound == True:
5555
self.initializeReceiveSocket()
5656

57+
def is_ready_to_send(self):
58+
return self.network is None or self.network.is_connected()
59+
5760
# EFFECTS: Sends the message to the cloud.
5861
def sendMessage(self, message, timeout=SEND_TIMEOUT):
5962

6063
try:
61-
if not self._networkManager.networkActive:
64+
if not self.is_ready_to_send():
6265
self.addPayloadToBuffer(message)
6366
return ''
6467

@@ -67,10 +70,14 @@ def sendMessage(self, message, timeout=SEND_TIMEOUT):
6770
self.logger.info("Sending message with body of length %d", len(message))
6871
self.logger.debug('Send: %s', message)
6972

70-
self.sock.send(message)
71-
self.logger.info('Sent.')
73+
resultbuf = ''
74+
if self.__to_use_at_sockets():
75+
resultbuf = self.network.send_message(message)
76+
else:
77+
self.sock.send(message)
78+
resultbuf = self.receive_send_socket()
7279

73-
resultbuf = self.receive_send_socket()
80+
self.logger.info('Sent.')
7481

7582
self.close_send_socket()
7683

@@ -96,23 +103,35 @@ def open_send_socket(self, timeout=SEND_TIMEOUT):
96103
self.logger.info("Connecting to: %s", self.send_host)
97104
self.logger.info("Port: %s", self.send_port)
98105

99-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
100-
self.sock.settimeout(timeout)
101-
self.sock.connect((self.send_host, self.send_port))
106+
# Check if we're going to use the AT command version of sockets or the
107+
# native Python socket lib.
108+
if self.__to_use_at_sockets():
109+
self.network.create_socket()
110+
self.network.connect_socket(self.send_host, self.send_port)
111+
else:
112+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
113+
self.sock.settimeout(timeout)
114+
self.sock.connect((self.send_host, self.send_port))
115+
102116
self._is_send_socket_open = True
103117
except (IOError):
104118
self.logger.error('An error occurred while attempting to send the message to the cloud')
105119
self.logger.error('Please try again.')
106120

107121
def close_send_socket(self):
108122
try:
123+
# Check if we're going to use the AT command version of sockets or the
124+
# native Python socket lib.
125+
if self.__to_use_at_sockets():
126+
self.network.close_socket()
127+
else:
128+
try:
129+
self.sock.shutdown(socket.SHUT_RDWR)
130+
except socket.error:
131+
pass
132+
133+
self.sock.close()
109134

110-
try:
111-
self.sock.shutdown(socket.SHUT_RDWR)
112-
except socket.error:
113-
pass
114-
115-
self.sock.close()
116135
self._is_send_socket_open = False
117136
self.logger.info('Socket closed.')
118137
except (IOError):
@@ -163,6 +182,10 @@ def sendPeriodicMessage(self, interval, message, topics=None, timeout=5):
163182
def sendSMS(self, destination_number, message):
164183
raise NotImplementedError('Cannot send SMS via custom cloud')
165184

185+
186+
def __to_use_at_sockets(self):
187+
return self.network is not None and self.network.at_sockets_available
188+
166189
# EFFECTS: This threaded infinite loop shoud keep sending messages with the specified
167190
# interval.
168191
def _periodic_job_thread(self, interval, function, *args):
@@ -198,6 +221,10 @@ def openReceiveSocket(self):
198221

199222
self.__enforce_receive_host_and_port()
200223

224+
if self.__to_use_at_sockets():
225+
self.network.open_receive_socket(self.receive_port)
226+
return True
227+
201228
self._receive_cv.acquire()
202229
self._receive_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
203230
self._receive_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -246,10 +273,14 @@ def open_receive_socket_helper(self):
246273
# EFFECTS: Closes the inbound socket connection.
247274
def closeReceiveSocket(self):
248275

249-
self._receive_cv.acquire()
250-
251276
self.logger.info('Closing socket...')
252277

278+
if self.__to_use_at_sockets():
279+
self.network.close_socket()
280+
return
281+
282+
self._receive_cv.acquire()
283+
253284
self.socketClose = True
254285
self._receive_cv.release()
255286

@@ -260,12 +291,13 @@ def closeReceiveSocket(self):
260291
self._receive_socket.shutdown(socket.SHUT_RDWR)
261292
except socket.error:
262293
pass
263-
264294
self._receive_socket.close()
265-
self.logger.info('Socket closed.')
266295

267296
self._receive_cv.release()
268297

298+
self.logger.info('Socket closed.')
299+
300+
269301
def acceptIncomingConnection(self):
270302
# This threaded infinite loop shoud keep listening on an incoming connection
271303
while True:
@@ -320,6 +352,10 @@ def __incoming_connection_thread(self, clientsocket):
320352

321353
# EFFECTS: Returns the receive buffer and empties it.
322354
def popReceivedMessage(self):
355+
356+
if self.__to_use_at_sockets():
357+
return self.network.pop_received_message()
358+
323359
self._receive_buffer_lock.acquire()
324360

325361
if len(self._receive_buffer) == 0:

Hologram/HologramCloud.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from CustomCloud import CustomCloud
1515
from Authentication import *
1616
from Exceptions.HologramError import HologramError
17-
import Event
1817

1918
from HologramAuth.TOTPAuthentication import TOTPAuthentication
2019
from HologramAuth.SIMOTPAuthentication import SIMOTPAuthentication
@@ -69,6 +68,9 @@ def __init__(self, credentials, enable_inbound=False, network='',
6968

7069
self.setAuthenticationType(credentials, authentication_type=authentication_type)
7170

71+
if self.authenticationType == 'totp':
72+
self.__populate_totp_credentials()
73+
7274
# EFFECTS: Authentication Configuration
7375
def setAuthenticationType(self, credentials, authentication_type='csrpsk'):
7476

@@ -82,36 +84,30 @@ def setAuthenticationType(self, credentials, authentication_type='csrpsk'):
8284
# EFFECTS: Sends the message to the cloud.
8385
def sendMessage(self, message, topics = None, timeout = 5):
8486

85-
if not self._networkManager.networkActive:
87+
if not self.is_ready_to_send():
8688
self.addPayloadToBuffer(message)
8789
return ''
8890

91+
# Set the appropriate credentials required for sim otp authentication.
92+
if self.authenticationType == 'sim-otp':
93+
self.__populate_sim_otp_credentials()
94+
8995
modem_type = None
9096
modem_id = None
9197
if self.network is not None:
9298
modem_id = self.network.modem_id
9399
modem_type = str(self.network.modem)
94100

95-
# Set the approriate credentials required for sim otp authentication.
96-
if self.authenticationType == 'sim-otp':
97-
nonce = self.request_nonce()
98-
command = self.authentication.generate_sim_otp_command(imsi=self.network.imsi,
99-
iccid=self.network.iccid,
100-
nonce=nonce)
101-
modem_response = self.network.get_sim_otp_response(command)
102-
self.authentication.generate_sim_otp_token(modem_response)
103-
elif self.authenticationType == 'totp':
104-
self.authentication.credentials['device_id'] = self.network.iccid
105-
self.authentication.credentials['private_key'] = self.network.imsi
106-
107101
output = self.authentication.buildPayloadString(message,
108102
topics=topics,
109103
modem_type=modem_type,
110104
modem_id=modem_id,
111105
version=self.version)
112106

113107
result = super(HologramCloud, self).sendMessage(output, timeout)
108+
return self.__parse_result(result)
114109

110+
def __parse_result(self, result):
115111
resultList = None
116112
if self.authenticationType == 'csrpsk':
117113
resultList = self.__parse_hologram_json_result(result)
@@ -120,8 +116,24 @@ def sendMessage(self, message, topics = None, timeout = 5):
120116

121117
return resultList[0]
122118

119+
def __populate_totp_credentials(self):
120+
try:
121+
self.authentication.credentials['device_id'] = self.network.iccid
122+
self.authentication.credentials['private_key'] = self.network.imsi
123+
except Exception as e:
124+
self.logger.error('Unable to fetch device id or private key')
125+
126+
def __populate_sim_otp_credentials(self):
127+
nonce = self.request_nonce()
128+
command = self.authentication.generate_sim_otp_command(imsi=self.network.imsi,
129+
iccid=self.network.iccid,
130+
nonce=nonce)
131+
modem_response = self.network.get_sim_otp_response(command)
132+
self.authentication.generate_sim_otp_token(modem_response)
133+
123134
def sendSMS(self, destination_number, message):
124135

136+
self.__enforce_authentication_type_supported()
125137
self.__enforce_valid_destination_number(destination_number)
126138
self.__enforce_max_sms_length(message)
127139

@@ -206,6 +218,10 @@ def __enforce_valid_destination_number(self, destination_number):
206218
if not destination_number.startswith('+'):
207219
raise HologramError('SMS destination number must start with a \'+\' sign')
208220

221+
def __enforce_authentication_type_supported(self):
222+
if self.authenticationType is not 'csrpsk':
223+
raise HologramError('%s does not support SDK SMS features' % self.authenticationType)
224+
209225
# REQUIRES: A result code (int).
210226
# EFFECTS: Returns a translated string based on the given hologram result code.
211227
def getResultString(self, result_code):

0 commit comments

Comments
 (0)