Compare commits

...

4 Commits

Author SHA1 Message Date
becd34d053 no message 2015-11-18 20:35:00 -06:00
6dc8e1b054 commit to present 2015-11-12 23:14:00 -06:00
b5feffc2d1 commit 11/12/2015 2015-11-12 00:00:00 -06:00
8607f82837 commit 10/31/2015 2015-10-31 00:00:00 -06:00
24 changed files with 3553 additions and 575 deletions

View File

@@ -1,21 +1,33 @@
[refresh rates] [refresh rates]
refresh camera local = 2 refresh camera local = 10
refresh camera transmit = 5 refresh camera transmit = 15
refresh barometer local = 10 refresh barometer local = 2
refresh barometer transmit = 5 refresh barometer transmit = 2
refresh gps local = 2 refresh gps local = 2
refresh gps transmit = 5 refresh gps transmit = 5
[camera settings] refresh system = 10
low quality resolution = (640, 360)
low quality compression pct = 20
[report settings] [camera settings]
report url = "http://10.0.1.4:5010/report" low quality resolution = (320, 240)
report image url = "http://10.0.1.4:5010/photo" low quality compression pct = 7
high quality resolution = (2592,1944)
high quality compression pct = 100
[modem settings] [modem settings]
com port name = "/dev/ttyAMA0" com port name = "/dev/ttyAMA0"
baud rate = "9600" baud rate = 9600
[server settings]
use_lan = True
url = http://home.ascorrea.com
server_port = 5010
data_path = upload-data
image_path = missions
ping_path = ping
[local storage settings]
photo path = /home/pi/scripts/spaceballoon/photos
log path = /home/pi/scripts/spaceballoon/logs

35
config.py Normal file
View File

@@ -0,0 +1,35 @@
__author__ = 'asc'
import configparser
config = configparser.ConfigParser()
config.sections()
config.read('config.ini')
refresh_camera_local = int(config['refresh rates']['refresh camera local'])
refresh_camera_transmit = int(config['refresh rates']['refresh camera transmit'])
refresh_barometer_local = int(config['refresh rates']['refresh barometer local'])
refresh_barometer_transmit = int(config['refresh rates']['refresh barometer transmit'])
refresh_gps_local = int(config['refresh rates']['refresh gps local'])
refresh_gps_transmit = int(config['refresh rates']['refresh gps transmit'])
refresh_system = int(config['refresh rates']['refresh system'])
low_quality_resolution = eval(config['camera settings']['low quality resolution'])
low_quality_compression_pct = int(config['camera settings']['low quality compression pct'])
high_quality_resolution = eval(config['camera settings']['high quality resolution'])
high_quality_compression_pct = int(config['camera settings']['high quality compression pct'])
use_lan = config['server settings']['use_lan']
url = config['server settings']['url']
server_port = int(config['server settings']['server_port'])
data_path = config['server settings']['data_path']
image_path = config['server settings']['image_path']
ping_path = config['server settings']['ping_path']
com_port_name = config['modem settings']['com port name']
baud_rate = config['modem settings']['baud rate']
photo_path = config['local storage settings']['photo path']
log_path = config['local storage settings']['log path']

154
data_handling_legacy.py Normal file
View File

@@ -0,0 +1,154 @@
__author__ = 'asc'
logger
def log(self, message, message_type="text"):
if message_type == "text":
print ("%s - Log Message: %s"% (str(datetime.datetime.now()), message))
elif message_type == "image":
image = message
if image:
filename = datetime.datetime.now().strftime("Image %Y%m%d-%H%M%S.jpg")
with open(os.path.join(self.photo_path, filename), 'wb') as f:
f.write(message)
return 'success'
elif message_type == "data":
message["sent"] = str(datetime.datetime.now())
file=self.log_path
header = add_keys_if_necessary(file, message)
keys = header
log=open(file, 'a')
writer = csv.DictWriter(log, keys, extrasaction="ignore")
writer.writerow(message)
log.close()
else:
raise Exception
def _send_data(self, message, message_type):
if message_type == "ping":
#TODO send text
contentType='text/xml'
if self.use_lan:
logger.info("Sending ping using LAN")
req = request.Request("{0}:{1}/{2}".format(self.url, self.server_port, self.ping_path))
req.add_header('Content-Type', contentType)
response = request.urlopen(req,json.dumps(message).encode())
response = response.read()
pass
elif not self.use_lan:
logger.info("Sending ping using modem")
# logger.debug("attaching GPRS")
#
# if not self.inet.attachGPRS("wholesale", "", "", 1):
# logger.error("error attaching GPRS")
# return False
logger.debug("posting")
if not self.inet.httpPOST(
self.url,
self.server_port,
"/{}".format(self.ping_path),
json.dumps(message),
contentType=contentType
):
logger.error("error making HTTP post: {0}".format(self.inet.errorText))
return False
response=self.inet.httpResponse
if response is not None:
response = str(self.inet.httpResponse).replace("\n\r", "\n")
else:
response = ("empty response")
elif message_type == "image":
response=None
m = MultipartEncoder(fields={'image': ('image', message, 'image/jpeg')})
if self.use_lan:
logger.info ("Sending image using LAN")
response = requests.post("{0}:{1}/{2}".format(self.url, self.server_port, self.image_path), data=m.read(), headers={'Content-Type': m.content_type})
pass
elif not self.use_lan:
logger.info ("Sending image using modem")
# logger.debug("attaching GPRS")
# if not self.inet.attachGPRS("wholesale", "", "", 1):
# logger.error("error attaching GPRS")
# return False
logger.debug("ip = {0}".format(self.inet.ip))
logger.debug("making HTTP POST request")
if not self.inet.httpPOST(
self.url,
self.server_port,
"/{}".format(self.image_path),
m.to_string(),
contentType=m.content_type
):
logger.error("error making HTTP POST: {0}".format(self.inet.errorText))
return False
if self.inet.httpResponse is not None:
response = str(self.inet.httpResponse).replace("\n\r", "\n")
else:
response = "empty response"
elif message_type == "data":
contentType="application/json"
message["sent"] = str(datetime.datetime.now())
if self.use_lan:
logger.info("Sending data using LAN")
req = request.Request("{0}:{1}/{2}".format(self.url, self.server_port, self.data_path))
req.add_header('Content-Type', contentType)
response = request.urlopen(req,json.dumps(message).encode())
response = response.read()
pass
elif not self.use_lan:
logger.info("Sending data using modem")
logger.debug("posting")
if not self.inet.httpPOST(
self.url,
self.server_port,
"/{}".format(self.data_path),
json.dumps(message),
contentType=contentType
):
logger.error("error making HTTP POST: {0}".format(self.inet.errorText))
return False
if self.inet.httpResponse is not None:
response = str(self.inet.httpResponse).replace("\n\r", "\n")
else:
response = ("empty response")
def create_transpondence(self):
if self._transpondence is None:
self._transpondence={}
self._transpondence['mt']=self.mt.now()
def add_to_transpondence(self, data, info_type="data"):
if self._transpondence.get(info_type) is not None:
if type(self._transpondence.get(info_type)) is list:
self._transpondence[info_type].append(data)
else:
self._transpondence[info_type] = [self._transpondence[info_type]]
self._transpondence[info_type].append(data)
else:
self._transpondence[info_type]=[data]
self._transpondence

View File

@@ -6,226 +6,276 @@ import requests
import json import json
from cell_connection import initializeUartPort, baseOperations, initializeLogs from cell_connection import initializeUartPort, baseOperations, initializeLogs
import logging import logging
from requests_toolbelt import MultipartEncoder
# logging.basicConfig(level=logging.INFO) import csv
import base64
logger = logging.getLogger(__name__)
LOGGER_LEVEL=logging.DEBUG
CONSOLE_LOGGER_LEVEL=logging.DEBUG
SCRIPTDIR = os.path.dirname(os.path.abspath(__file__)) SCRIPTDIR = os.path.dirname(os.path.abspath(__file__))
logger = logging.getLogger(__name__)
class Datalogger_Debug (): def add_keys_if_necessary(existing_file, test_dict):
def __init__(self, path=SCRIPTDIR): all=None
pass
def log(self, message, type="text"):
if type == "text":
print ("%s - Log Message: %s"% (str(datetime.datetime.now()), message))
elif type == "image":
print ("%s - Log Image: %s"% (str(datetime.datetime.now()), message))
elif type == "data":
print ("%s - Log Message: %s"% (str(datetime.datetime.now()), message))
else:
raise Exception
class Datareporter_Debug ():
def __init__(self, path=SCRIPTDIR):
#TODO communication
pass
@property
def status (self):
return (0, "Data reporter functioning properly")
def send(self, message, type="text"):
if type == "text":
#TODO send text
print ("%s - Sent Message: %s"% (str(datetime.datetime.now()), message))
elif type == "image":
#todo send image
print ("%s - Sent Image: %s"% (str(datetime.datetime.now()), message))
else:
#todo error handling
raise Exception
class Datareporter_Debug2 ():
#Debug 2 sends to server
def __init__(self, path=SCRIPTDIR,
report_url = "http://10.0.1.4:5010/report",
report_image_url = "http://10.0.1.4:5010/photo",
com_port_name = "/dev/ttyAMA0",
baud_rate = "9600"):
self.report_url = report_url
self.report_image_url = report_image_url
self.com_port_name = com_port_name,
self.baud_rate = baud_rate
pass
@property
def status (self):
#TODO status check
try: try:
check = json.loads(request.urlopen(self.report_url).read().decode()).get('status') with open(existing_file,'r') as f:
pass reader = csv.reader(f)
if not check: header = []
return (0, "Data reporter functioning properly")
else:
return (1, check)
except Exception as e:
return (1, "Data reporter error: %s" % e)
def send(self, message, type="text"):
try: try:
if type == "text": header = next(reader)
#TODO send text except StopIteration as e:
print ("%s - Sent Message: %s"% (str(datetime.datetime.now()), message))
elif type == "image":
#todo send image
response = requests.post(self.report_url, files={'file': message})
print ("%s - Sent Image: %s"% (str(datetime.datetime.now()), message))
elif type == "data":
#add date to message
message['sent']=str(datetime.datetime.now())
req = request.Request(self.report_image_url)
req.add_header('Content-Type', 'application/json')
response = request.urlopen(req,json.dumps(message).encode())
the_page = response.read()
return 0
except Exception as e:
#todo error handling
pass pass
class Datareporter_Debug3 (): if len(header) == 0:
#Debug 2 sends from cell to server header = list(test_dict.keys())
writer = csv.DictWriter(existing_file, test_dict.keys(), extrasaction='ignore')
writer.writeheader()
# if not header >= list(test_dict.keys()) or not header <= list(test_dict.keys()):
if len(set (header)-set(test_dict.keys()))>0:
existing_keys =set (header)
data_keys = set (test_dict.keys())
keys_to_add = data_keys - existing_keys
all = []
for key in keys_to_add:
header.append(key)
all.append(header)
for row in reader:
all.append(row)
pass
except (OSError, IOError, TypeError) as e:
header = list(test_dict.keys())
f=open(existing_file, 'a+')
writer = csv.DictWriter(f, test_dict.keys(), extrasaction='ignore')
writer.writeheader()
f.close()
if all is not None:
with open (existing_file, 'w') as f:
writer = csv.writer(f)
writer.writerows(all)
return header
class Datalogger():
def __init__(self,
missiontime,
text_path,
log_path,
photo_path):
self._mt = missiontime
self.text_path=text_path
self.log_path=os.path.join(log_path, 'MISSION {}.csv'.format(self._mt.name))
self.photo_path=photo_path
pass
def log(self, r):
self._record = r._get_dict()
# logger.info('Recording {}'.format(self._record))
for k in self._record.keys():
if k is 'b':
if self._record[k] is not list:
l = [self._record[k]]
else:
l = self._record[k]
for item in l:
logger.debug("item: {}".format(self._record), extra={'MISSION_TIME': self._mt.now(), 'MISSION_ID':self._mt.name})
item['at']=self._mt.to_absolutetime(item['mt'])
item['mid']=self._mt.name
header = add_keys_if_necessary(self.log_path, item)
keys = header
log=open(self.log_path, 'a')
writer = csv.DictWriter(log, keys, extrasaction='ignore')
writer.writerow(item)
log.close()
logger.info('Barometer data recorded',extra={'MISSION_TIME': self._mt.now(), 'MISSION_ID':self._mt.name})
elif k is 'i':
if self._record[k] is tuple:
l = [self._record[k]]
else:
l = self._record[k]
for item in l:
# Form is ('name', file object, 'type')
filename = item[0]
file = base64.b64decode(bytes(item[1],'ascii'))
folder = os.path.join(self.photo_path, self._mt.mid)
file_path = os.path.join(self.photo_path, folder, filename)
if not os.path.exists(folder):
os.makedirs(folder)
with open(file_path, 'wb') as f:
f.write(file)
# file.save(os.path.join(self.photo_path, filename))
logger.info('Image data recorded',extra={'MISSION_TIME': self._mt.now(), 'MISSION_ID':self._mt.name})
return 'success'
class Datareporter():
from lib.sim900.inetgsm import SimInetGSM from lib.sim900.inetgsm import SimInetGSM
def __init__(self, path=SCRIPTDIR, def __init__(self,
report_url = "http://10.0.1.4:5010/report", missiontime,
report_image_url = "http://10.0.1.4:5010/upload-file", url,
com_port_name = "/dev/ttyAMA0", data_path,
baud_rate = "9600", image_path,
content_type = None): ping_path,
server_port,
com_port_name=None,
baud_rate=None,
use_lan = False,
path=SCRIPTDIR):
#TODO communication #TODO communication
self.report_url = report_url self.mt = missiontime
self.report_image_url = report_image_url self.url = url
self.server_port=server_port
self.image_path = image_path
self.data_path = data_path
self.com_port_name = com_port_name self.com_port_name = com_port_name
self.baud_rate = baud_rate self.baud_rate = baud_rate
self.content_type = content_type self.ping_path = ping_path
self.use_lan = use_lan
self._transpondence=None
if not use_lan:
self.port = initializeUartPort(portName=self.com_port_name, baudrate=self.baud_rate)
d = baseOperations(self.port, logger)
pass if not d is None:
(self.gsm, self.imei) = d
self.inet = self.SimInetGSM(self.port, logger)
logger.info('ip = {0}'.format(self.inet.ip), extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
logger.debug('attaching GPRS', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
if not self.inet.attachGPRS('wholesale', '', '', 1):
logger.error('error attaching GPRS', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
return False
#register mission number to server
intiate = Record(str(self.mt.timezero),'zt')
# intiate.add(self.mt.mid,'mid')
self.send(intiate, _intiating_report=True)
@property @property
def status (self): def status (self):
#TODO status check #TODO status check
try: try:
check = json.loads(request.urlopen(self.report_url).read().decode()).get('status')
pass return (0, 'Data reporter functioning properly')
if not check:
return (0, "Data reporter functioning properly")
else:
return (1, check)
except Exception as e: except Exception as e:
return (1, "Data reporter error: %s" % e) return (1, 'Data reporter error: %s' % e)
def send(self, message, type="text"): def _send_data(self, message, _intiating_report=False):
if type == "text": response=None
#TODO send text m = MultipartEncoder(fields=message)
print ("%s - Sent Message: %s"% (str(datetime.datetime.now()), message))
elif type == "image":
#todo send image
# response = requests.post(self.report_image_url, files={'file': message})
print ("%s - Sent Image: %s"% (str(datetime.datetime.now()), message))
#adding & initializing port object if _intiating_report:
port = initializeUartPort(portName=self.com_port_name, baudrate=self.baud_rate) path = '{0}'.format(self.image_path)
#initializing logger
(formatter, logger, consoleLogger,) = initializeLogs(LOGGER_LEVEL, CONSOLE_LOGGER_LEVEL)
#making base operations
d = baseOperations(port, logger)
if d is None:
return False
(gsm, imei) = d
inet = self.SimInetGSM(port, logger)
logger.info("attaching GPRS")
if not inet.attachGPRS("wholesale", "", "", 1):
logger.error("error attaching GPRS")
return False
logger.info("ip = {0}".format(inet.ip))
#making HTTP GET request
logger.info("making HTTP POST request")
if not inet.httpPOST_DATA(
"home.ascorrea.com",
5010,
"/upload-file",
# content=self.content_type,
parameters="{0}".format(message)
):
logger.error("error making HTTP GET post: {0}".format(inet.errorText))
return False
logger.info("httpResult = {0}".format(inet.httpResult))
if inet.httpResponse is not None:
response = str(inet.httpResponse).replace("\n\r", "\n")
logger.info("response: \"{0}\"".format(response))
else: else:
logger.info("empty response") path = '{0}/{1}'.format(self.image_path,self.mt.mid)
elif type == "data": if self.use_lan:
#adding & initializing port object logger.info ('Sending transpondence using LAN', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
port = initializeUartPort(portName=self.com_port_name, baudrate=self.baud_rate) response = requests.post('{0}:{1}/{2}'.format(self.url, self.server_port, path), data=m.read(), headers={'Content-Type': m.content_type})
pass
#initializing logger elif not self.use_lan:
(formatter, logger, consoleLogger,) = initializeLogs(LOGGER_LEVEL, CONSOLE_LOGGER_LEVEL) logger.info ('Sending transpondence using modem', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
# logger.debug('attaching GPRS')
# if not self.inet.attachGPRS('wholesale', '', '', 1):
# logger.error('error attaching GPRS')
# return False
#making base operations logger.debug('ip = {0}'.format(self.inet.ip), extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
d = baseOperations(port, logger)
if d is None:
return False
(gsm, imei) = d logger.debug('making HTTP POST request', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
inet = self.SimInetGSM(port, logger) if not self.inet.httpPOST(
self.url,
logger.info("attaching GPRS") self.server_port,
if not inet.attachGPRS("internet", "", "", 1): "/{}".format(path),
logger.error("error attaching GPRS") m.to_string(),
return False contentType=m.content_type
logger.info("ip = {0}".format(inet.ip))
#making HTTP GET request
logger.info("making HTTP POST request")
if not inet.httpPOST(
"home.ascorrea.com",
5010,
"/report-encoded",
parse.urlencode(message)
): ):
logger.error("error making HTTP GET post: {0}".format(inet.errorText)) logger.error('error making HTTP POST: {0}'.format(self.inet.errorText), extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
return False return False
logger.info("httpResult = {0}".format(inet.httpResult)) if self.inet.httpResponse is not None:
if inet.httpResponse is not None: response = str(self.inet.httpResponse).replace('\n\r', '\n')
response = str(inet.httpResponse).replace("\n\r", "\n")
logger.info("response: \"{0}\"".format(response))
else: else:
logger.info("empty response") response = 'empty response'
if _intiating_report:
if type (response) is str:
mid = response
else:
mid = response.text
self.mt.set_mid(mid)
return response
def send(self, t, _intiating_report=False):
self._transpondence = t._get_dict()
self._transpondence['mt'] = self.mt.now()
# print ('Send transpondence {}'.format(self._transpondence))
for k in self._transpondence.keys():
# if self._transpondence[k] is list or self._transpondence[k] is dict or self._transpondence[k] is float:
if type(self._transpondence) is dict:
self._transpondence[k] = json.dumps(self._transpondence[k])
r = self._send_data(message=self._transpondence, _intiating_report=_intiating_report)
#On error, do not clear transpondence
if not r:
self._transpondence = self._transpondence
return None
class Record():
def __init__(self,first_item=None, first_item_type=None):
# if self._transpondence is None:
self._transpondence={}
if first_item and first_item_type:
self.add(first_item, first_item_type)
def add(self, data, info_type='data'):
if type(data) is not list:
if self._transpondence.get(info_type) is not None:
if type(self._transpondence.get(info_type)) is list:
self._transpondence[info_type].append(data)
else:
self._transpondence[info_type] = [self._transpondence[info_type]]
self._transpondence[info_type].append(data)
else:
self._transpondence[info_type]=data
else:
if self._transpondence.get(info_type) is not None:
if type(self._transpondence.get(info_type)) is list:
self._transpondence[info_type] + data
else:
self._transpondence[info_type] = [self._transpondence[info_type]]
self._transpondence[info_type] + data
else:
self._transpondence[info_type]=data
def _get_dict(self):
return self._transpondence

View File

@@ -1,16 +1,100 @@
__author__ = 'asc' __author__ = 'asc'
debug = True debug = True
from random import random from random import random
import Adafruit_BMP.BMP085 as BMP085
import picamera
import configparser import configparser
import time import time
import logging import logging
import base64
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Barometer_Debug (): class Camera:
def __init__(self): def __init__(self,
low_quality_resolution=(320,240),
low_quality_compression_pct=5,
high_quality_resolution=(2592,1944),
high_quality_compression_pct=85,
debug = False,
**kwargs):
logger.info("Initializing camera", extra={'MISSION_TIME': "", 'MISSION_ID':""})
time.sleep(1)
self.low_quality_resolution = low_quality_resolution
self.low_quality_compression_pct = low_quality_compression_pct
self.high_quality_resolution = high_quality_resolution
self.high_quality_compression_pct = high_quality_compression_pct
self.kwargs=kwargs
self._debug = debug
if not self._debug:
import picamera
self._cam = picamera.PiCamera(resolution=high_quality_resolution)
# if "vflip" in kwargs.keys():
for k in kwargs.keys():
setattr(self._cam, k, kwargs.get(k))
logger.debug("Camera intialized", extra={'MISSION_TIME': "", 'MISSION_ID':""})
pass
@property
def status (self):
return (0, "Camera functioning properly")
def capture (self, name, no_low_quality=False, no_high_quality=False, **kwargs):
#todo image adjustments
img_hi = None
img_lo = None
r = {}
if not self._debug:
if not no_high_quality:
logger.debug('Taking high quality photo', extra={'MISSION_TIME': "", 'MISSION_ID':""})
self._cam.capture("temp_img_hi.jpg",
resize=self.high_quality_resolution,
quality=self.high_quality_compression_pct,
**kwargs
)
with open("temp_img_hi.jpg", 'rb') as f:
# img_hi = base64.b64encode(f.read())
img_hi = str(base64.b64encode(f.read()),'ascii')
r['hi']=("{}_hi.jpg".format(name), img_hi, "image/jpeg")
logger.debug('High quality photo taken', extra={'MISSION_TIME': "", 'MISSION_ID':""})
if not no_low_quality:
time.sleep(1)
logger.debug('Taking low quality photo (Resolution: {}, JPEG Quality: {}%)'.format(self.low_quality_resolution, self.low_quality_compression_pct))
self._cam.capture("temp_img_lo.jpg",
resize=self.low_quality_resolution,
quality=self.low_quality_compression_pct,
**kwargs
)
with open("temp_img_lo.jpg", 'rb') as f:
img_lo = str(base64.b64encode(f.read()),'ascii')
# img_lo = str(f.read())
r['lo']=("{}_lo.jpg".format(name), img_lo, "image/jpeg")
logger.debug('Low quality photo taken', extra={'MISSION_TIME': "", 'MISSION_ID':""})
else:
if not no_high_quality:
with open("temp_img_hi.jpg", 'rb') as f:
img_hi = str(base64.b64encode(f.read()),'ascii')
# img_hi = str(f.read(),'ascii')
r['hi']=("{}_hi.jpg".format(name), img_hi, "image/jpeg")
logger.info('High quality photo taken', extra={'MISSION_TIME': "", 'MISSION_ID':""})
if not no_low_quality:
with open("temp_img_lo.jpg", 'rb') as f:
img_lo = str(base64.b64encode(f.read()),'ascii')
# img_lo = str(f.read())
r['lo']=("{}_lo.jpg".format(name), img_lo, "image/jpeg")
logger.info('Low quality photo taken', extra={'MISSION_TIME': "", 'MISSION_ID':""})
return r
class Barometer:
def __init__(self, debug=False):
logger.debug("Intializing barometer", extra={'MISSION_TIME': "", 'MISSION_ID':""})
if not debug:
import Adafruit_BMP.BMP085 as BMP085
self.bmp = BMP085.BMP085()
logger.debug("Barometer intilized", extra={'MISSION_TIME': "", 'MISSION_ID':""})
pass pass
@property @property
@@ -22,10 +106,7 @@ class Barometer_Debug ():
if self.status[0]: if self.status[0]:
return 'error' return 'error'
if debug: temp = self.bmp.read_temperature()
temp = random()*100
else:
raise Exception ('Not Debug')
return temp return temp
@property @property
@@ -33,80 +114,43 @@ class Barometer_Debug ():
if (self.status[0]): if (self.status[0]):
return 'error' return 'error'
if debug: # press = 100000*random()
press = random() press = self.bmp.read_pressure()
else:
raise Exception ('Not Debug')
return press return press
@property @property
def altitude (self): def altitude (self):
if self.status[0]: if self.status[0]:
return 'error' return 'error'
if debug: #TODO Set the altitude of your current location in meter
alt = random()*100000 alt = self.bmp.read_altitude()
else:
raise Exception ('Not Debug')
return alt return alt
class Camera_Debug(): def read(self):
logger.debug("Reading from barometer", extra={'MISSION_TIME': "", 'MISSION_ID':""})
#refresh each instrument
if not debug:
alt = self.altitude
press = self.pressure
temp = self.temperature
else:
temp = random()*100
press = random()
alt = random()*100000
result = {"a":alt,
"t":temp,
"p":press,
}
logger.debug("Barometer reads {}".format(result), extra={'MISSION_TIME': "", 'MISSION_ID':""})
return result
class Gps:
def __init__(self): def __init__(self):
pass logger.debug("Intializing GPS")
# self.bmp = BMP085.BMP085()
@property logger.debug("Gps intilized")
def status (self):
return (0, "Camera functioning properly")
def capture (self):
#todo capture image
return ({"file":open("image2.jpg", 'rb')})
#camera debug 2 uses actual camera
class Camera_Debug2():
def __init__(self, low_quality_resolution,
low_quality_compression_pct,
high_quality_resolution=None,
high_qualitycompression_pct=85):
logger.debug("Initializing camera")
time.sleep(1)
self.low_quality_resolution = low_quality_resolution
self.low_quality_compression_pct = low_quality_compression_pct
self._cam = picamera.PiCamera()
logger.debug("Camera intialized")
pass
@property
def status (self):
return (0, "Camera functioning properly")
def capture (self, no_low_quality=False, no_high_quality=False):
#todo image adjustments
img_hi = None
img_lo = None
if not no_high_quality:
logger.debug('Taking high quality photo')
self._cam.capture("temp_img_hi.jpg")
img_hi = open("temp_img_hi.jpg", 'rb')
logger.debug('High quality photo taken, file: {}'.format(img_hi))
if not no_low_quality:
logger.debug('Taking low quality photo (Resolution: {}, JPEG Quality: {}%'.format(self.low_quality_resolution, self.low_quality_compression_pct))
self._cam.capture("temp_img_lo.jpg",
resize=self.low_quality_resolution,
quality=self.low_quality_compression_pct)
img_lo = open("temp_img_lo.jpg", 'rb')
logger.debug('Low quality photo taken, file: {}'.format(img_lo))
return ({"hi":img_hi,
"lo":img_lo})
#barometerdebug2 uses actual barometer
class Barometer_Debug2 ():
def __init__(self):
logger.debug("Intializing barometer")
self.bmp = BMP085.BMP085()
logger.debug("Barometer intilized")
pass pass
@property @property
@@ -145,13 +189,15 @@ class Barometer_Debug2 ():
alt = self.altitude alt = self.altitude
press = self.pressure press = self.pressure
temp = self.temperature temp = self.temperature
result = {"altitude":alt, result = {"a":alt,
"temperature":temp, "t":temp,
"pressure":press, "p":press,
} }
logger.debug("Barometer reads {}".format(result)) logger.debug("Gps reads {}".format(result))
return result return result
# class Gps:

View File

View File

View File

@@ -0,0 +1,60 @@
__author__ = 'Bohdan'
import time
class AminisLastErrorHolder:
def __init__(self):
self.errorText = ""
self.__hasError = False
def clearError(self):
self.errorText = ""
self.__hasError = False
def setError(self, errorText):
self.errorText = errorText
self.__hasError = True
@property
def hasError(self):
return self.__hasError
def timeDelta(timeBegin):
end = time.time()
secs = end - timeBegin
msecs = (end - timeBegin) * 1000.0
return secs*1000 + msecs
def splitAndFilter(value, separator):
items = str(value).split(separator)
ret = []
for item in items:
item = str(item).strip()
if len(item) == 0:
continue
ret += [item]
return ret
def isFloat(value):
try:
float(value)
return True
except ValueError:
return False
def strToFloat(value):
value = str(value).strip()
if len(value) == 0:
return None
value = value.replace(",", ".")
try:
return float(value)
except ValueError:
return None

661
lib/sim900.bak/gsm.py Normal file
View File

@@ -0,0 +1,661 @@
#The MIT License (MIT)
#
#Copyright (c) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua )
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
"""
This file is part of sim-module package. Implements basic functions of SIM900 modules.
sim-module package allows to communicate with SIM 900 modules: send SMS, make HTTP requests and use other
functions of SIM 900 modules.
Copyright (C) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua ) All Rights Reserved.
"""
import time
import serial
import logging
from lib.sim900.simshared import *
class GsmSpecialCharacters:
ctrlz = 26 #//Ascii character for ctr+z. End of a SMS.
cr = 0x0d #//Ascii character for carriage return.
lf = 0x0a #//Ascii character for line feed.
class SimGsmState:
UNKNOWN = 0
ERROR = 1
IDLE = 2
READY = 3
ATTACHED = 4
TCPSERVERWAIT = 5
TCPCONNECTEDSERVER = 6
TCPCONNECTEDCLIENT = 7
class SimGsmPinRequestState:
UNKNOWN = -1
NOPINNEEDED = 0
SIM_PIN = 1
SIM_PUK = 2
PH_SIM_PIN = 3
PH_SIM_PUK = 4
SIM_PIN2 = 5
SIM_PUK2 = 6
class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging):
def __init__(self, serial, logger = None):
AminisLastErrorHolderWithLogging.__init__(self, logger)
self.input = bytearray()
self.__serial = serial
#stores last executed command result
self.lastResult = None
def openPort(self):
try:
self.__serial.open()
self.flush()
except Exception as e:
self.setError("exception till port openning: {0}".format(e))
return False
except:
self.setError("error opening port")
return False
return True
def __sendRawBytes(self, data, maxWaitTime = 1000):
"""
Sends raw bytes to the SIM module
:param data: data which must be send
:param maxWaitTime: max wait time for sending sequence
:return: True if data was send, otherwise returns False
"""
bytesToSend = len(data)
sentBytes = 0
start = time.time()
self.logger.debug("{0}, sending: {1}".format(inspect.stack()[0][3], data))
while sentBytes < bytesToSend:
if timeDelta(start) >= maxWaitTime:
self.setWarn("__sendRawBytes(): timed out")
return False
sentBytes += self.__serial.write(data[sentBytes : ])
if sentBytes == 0:
time.sleep(0.001)
continue
return True
def print(self, commandString, encoding = "ascii"):
"""
Sends string data to the SIM module
:param commandString: data what must be sent
:param encoding: before sending string it will be converted to the bytearray with this encoding
:return: True if everything is OK, otherwise returns false
"""
data = bytearray(commandString, encoding)
return self.__sendRawBytes(data)
def simpleWrite(self, commandLine, encoding = "ascii"):
"""
Just alias for print() method
:param commandLine: data which must be sent
:param encoding: before sending string it will be converted to the bytearray with this encoding
:return: True if data sent, otherwise returns False
"""
return self.print(commandLine, encoding)
def printLn(self, commandString, encoding = "ascii"):
"""
Sends string data and CR/LF in the end to the SIM module
:param commandString: data which must be sent
:param encoding: before sending string it will be converted to the bytearray with this encoding
:return: True if data sent, otherwise returns False
"""
# print('am in println: {}'.format(commandString))
if type(commandString) is bytearray:
data = commandString + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
else:
data = bytearray(commandString, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
r = self.__sendRawBytes(data)
# print('am leavin println: {}'.format(data))
return r
def simpleWriteLn(self, commandLine, encoding = "ascii"):
"""
Just alias for printLn() method
:param commandLine: data which must be sent
:param encoding: before sending string it will be converted to the bytearray with this encoding
:return: True if data sent, otherwise returns False
"""
# print('am in simplewriteln')
r = self.printLn(commandLine, encoding)
# print('am leaving simplewriteln')
return r
def flushInput(self):
"""
Flushes input buffer
:return: nothing
"""
try:
self.__serial.flushInput()
except Exception as e:
self.setError("error flushing: {0}".format(e))
except:
self.setError("error flushing")
def flushOutput(self):
"""
Flushes output buffer
:return: nothing
"""
try:
self.__serial.flushOutput()
except Exception as e:
self.setError("error flushing: {0}".format(e))
except:
self.setError("error flushing")
def readFixedSzieByteArray(self, bytesCount, maxWaitTime):
start = time.time()
buffer = bytearray()
try:
while True:
#checking for timeout
if timeDelta(start) >= maxWaitTime:
return None
receivedBytesQty = 0
while True:
bytesToRead = 10 if ((bytesCount - len(buffer)) >= 10) else 1
b = self.__serial.read(bytesToRead)
if (b is None) or (len(b) == 0):
break
buffer += bytearray(b)
receivedBytesQty += len(b)
if len(buffer) == bytesCount:
return buffer
#if we have nothing in input - let's go sleep for some time
if receivedBytesQty == 0:
time.sleep(0.003)
#comming there by timeout
return None
except Exception as e:
self.setError(e)
return None
except:
self.setError("reading error...")
return None
def readNullTerminatedLn(self, maxWaitTime = 5000, codepage = "ascii"):
start = time.time()
start = time.time()
buffer = bytearray()
try:
while True:
#checking for timeout
if timeDelta(start) >= maxWaitTime:
return None
receivedBytesQty = 0
while True:
b = self.__serial.read(1)
if (b is None) or (len(b) == 0):
break
#checking that we have NULL symbol in
idx = b.find(0x00)
if idx != -1:
buffer.extend(b[:idx])
return buffer.decode(codepage)
buffer += bytearray(b)
receivedBytesQty += len(b)
#if we have nothing in input - let's go sleep for some time
if receivedBytesQty == 0:
time.sleep(0.003)
#comming there by timeout
return None
except Exception as e:
self.setError(e)
return None
except:
self.setError("reading error...")
return None
def readLn(self, maxWaitTime = 5000, codepage = "ascii"):
"""
Returns text string from SIM module. Can return even empty strings.
:param maxWaitTime: max wait interval for operation
:param codepage: code page of result string
:return: received string
"""
start = time.time()
buffer = bytearray()
try:
while True:
#checking for timeout
if timeDelta(start) >= maxWaitTime:
return None
receivedBytesQty = 0
while True:
b = self.__serial.read(1)
if (b is None) or (len(b) == 0):
break
buffer += bytearray(b)
receivedBytesQty += len(b)
if codepage is not None:
#checking for line end symbols
line = buffer.decode(codepage)
if '\n' in line:
return line.strip()
elif ord('\n') in buffer:
return buffer
#if we have nothing in input - let's go sleep for some time
if receivedBytesQty == 0:
time.sleep(0)
#comming there by timeout
return None
except Exception as e:
self.setError(e)
return None
except:
self.setError("reading error...")
return None
def readDataLine(self, maxWaitTime = 500, codepage = "ascii"):
"""
Returns non empty data string. So, if it will receive empty string function will continue non empty string
retrieving
:param maxWaitTime: max wait time for receiving
:param codepage: code page of result string, if it's a None - will return a bytearray
:return: received string
"""
ret = None
start = time.time()
while True:
#checking for timeout
if timeDelta(start) >= maxWaitTime:
break
#reading string
#TODO: need to fix timeout (substract already spent time interval)
line = self.readLn(maxWaitTime, codepage)
#removing garbage symbols
if line is not None:
line = str(line).strip()
#if we have non empty string let's return it
if len(line) > 0:
return line
else:
#if we have empty line - let's continue reading
continue
else:
#returning None if None received
if line is None:
return None
continue
#we will come here by timeout
return None
def flush(self):
"""
Flushes input and output buffers
:return: nothing
"""
try:
self.__serial.flush()
except Exception as e:
self.setError("error flushing: {0}".format(e))
except:
self.setError("error flushing")
def closePort(self):
"""
Closes COM port
:return: nothing
"""
try:
self.__serial.close()
except Exception as e:
self.setError("error closing port: {0}".format(e))
except:
self.setError("error closing port")
@staticmethod
def isCrLf(symbol):
"""
Returns True when parameter is CR/LF symbol, otherwise returns False
:param symbol: symbol for analysis
:return: True when CR/LF symbol, otherwise returns False
"""
return (symbol == GsmSpecialCharacters.cr) or (symbol == GsmSpecialCharacters.lf)
@staticmethod
def getLastNonEmptyString(strings):
"""
Parses strings array and returns last non empty string from array
:param strings: strings array for analysis
:return: last non empty string, otherwise None
"""
#if there is no data - returning None
if strings is None:
return None
qty = len(strings)
if qty == 0:
return None
#looking for last non empty string
for i in range(qty):
s = str(strings[-(i+1)]).strip()
if len(s) > 0:
return s
return None
@staticmethod
def removeEndResult(strings, targetString):
"""
Searches and removes last string which contains result
:param strings:
:param targetString:
:return:
"""
ret = ""
#searching for target string
while len(strings) > 0:
s = str(strings[-1]).strip()
strings.pop(len(strings)-1)
if s == targetString:
break
#compiling result
qty = len(strings)
for i in range(qty):
ret += strings[i]
return ret
@staticmethod
def parseStrings(buffer, encoding = "ascii"):
"""
Parses string (from given encoding), looks for cr/lf and retutrns strings array
:param buffer: input string
:param encoding: encoding
:return: strings array
"""
#decoding
bigString = buffer.decode(encoding)
#searching for cr/lf and making strings array
if "\r" in bigString:
ret = bigString.split("\r")
else:
ret = [bigString]
return ret
def commandAndStdResult(self, commandText, maxWaitTime = 10000, possibleResults = None):
self.lastResult = None
#setting up standard results
if possibleResults is None:
possibleResults = ["OK", "ERROR"]
start = time.time()
buffer = bytearray()
self.flush()
#sending command
self.simpleWriteLn(commandText)
try:
while True:
if timeDelta(start) >= maxWaitTime:
break
readBytesQty = 0
while True:
b = self.__serial.read(100)
if (b is not None) and (len(b) >= 1):
buffer += bytearray(b)
self.logger.debug("{0}: buffer = {1}".format(inspect.stack()[0][3], buffer))
readBytesQty += len(b)
continue
else:
break
#if we have no data - let's go sleep for tiny amount of time
if readBytesQty == 0:
time.sleep(0.005)
continue
#parsing result strings
strings = SimGsm.parseStrings(buffer[:])
self.logger.debug("{0}: strings = {1}".format(inspect.stack()[0][3], strings))
if strings is None:
time.sleep(0.01)
continue
#if we have some strings let's parse it
if len(strings) > 0:
lastString = SimGsm.getLastNonEmptyString(strings[:])
if lastString in possibleResults:
self.lastResult = lastString
return SimGsm.removeEndResult(strings[:], lastString)
time.sleep(0.05)
return None
except Exception as e:
self.setError(e)
return None
except:
self.setError("reading error...")
return None
def execSimpleCommand(self, commandText, result, timeout = 500):
ret = self.commandAndStdResult(commandText, timeout, [result])
if (ret is None) or (self.lastResult != result):
return False
return True
def execSimpleOkCommand(self, commandText, timeout = 500):
self.logger.debug("executing command '{0}'".format(commandText))
ret = self.commandAndStdResult(commandText, timeout, ["OK", "ERROR"])
if (ret is None) or (self.lastResult != "OK"):
return False
return True
def execSimpleCommandsList(self, commandsList):
for command in commandsList:
if not self.execSimpleOkCommand(command[0], command[1]):
return False
return True
class SimGsm(SimGsmSerialPortHandler):
def __init__(self, serial, logger = None):
SimGsmSerialPortHandler.__init__(self, serial, logger)
self.__state = SimGsmState.UNKNOWN
self.pinState = SimGsmPinRequestState.UNKNOWN
def begin(self, numberOfAttempts = 5):
ok = False
self.flush()
needDisableEcho = False
for i in range(numberOfAttempts):
self.printLn("AT")
line = self.readDataLine(2000, "ascii")
#if we do not have something in input - let's go sleep
if line is None:
time.sleep(0.2)
continue
#we have echo, need to reconfigure
if line == "AT":
#we have ECHO, need reconfigure
needDisableEcho = True
line = self.readDataLine(500, "ascii")
if line == "OK":
ok = True
break
elif line == "OK":
ok = True
break
if not ok:
return False
#disabling echo if needed
if needDisableEcho:
self.logger.info("Disabling echo, calling 'ATE0'")
self.simpleWriteLn("ATE0")
time.sleep(0.5)
self.flush()
commands = [
["ATV1", 500], #short answer for commands
["AT+CMEE=0", 500], #disabling error report
["AT", 5000] #checking state
]
for cmd in commands:
self.logger.debug("configuring, calling: {0}".format(cmd[0]))
if not self.execSimpleOkCommand(commandText=cmd[0],timeout=cmd[1]):
return False
#checking PIN state
if not self.__checkPin():
return False
return True
def __checkPin(self):
msg = self.commandAndStdResult("AT+CPIN?")
if msg is None:
return False
if self.lastResult != "OK":
return False
msg = str(msg).strip()
values = splitAndFilter(msg, ":")
msg.split(":")
if len(values) < 2:
self.setError("Wrong response for PIN state request")
return False
if values[0] != "+CPIN":
self.setError("Wrong response for PIN state request. First value = '{0}'".format(values[0]))
return False
v = " ".join([v for v in values[1:]])
if v == "READY":
self.pinState = SimGsmPinRequestState.NOPINNEEDED
elif v == "SIM PIN":
self.pinState = SimGsmPinRequestState.SIM_PIN
elif v == "SIM PUK":
self.pinState = SimGsmPinRequestState.SIM_PUK
elif v == "PH_SIM PIN":
self.pinState = SimGsmPinRequestState.PH_SIM_PIN
elif v == "PH_SIM PUK":
self.pinState = SimGsmPinRequestState.PH_SIM_PUK
elif v == "SIM PIN2":
self.pinState = SimGsmPinRequestState.SIM_PIN2
elif v == "SIM PUK2":
self.pinState = SimGsmPinRequestState.SIM_PUK2
else:
self.pinState = SimGsmPinRequestState.UNKNOWN
self.setError("Unknown PIN request answer: {0}".format(v))
return False
return True
def enterPin(self, pinCode):
return self.execSimpleOkCommand("AT+CPIN=\"{0}\"".format(pinCode))

45
lib/sim900.bak/imei.py Normal file
View File

@@ -0,0 +1,45 @@
#The MIT License (MIT)
#
#Copyright (c) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua )
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
"""
This file is part of sim-module package. Can be used for SIM900 module IMEI retrieving
sim-module package allows to communicate with SIM 900 modules: send SMS, make HTTP requests and use other
functions of SIM 900 modules.
Copyright (C) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua ) All Rights Reserved.
"""
from lib.sim900.gsm import SimGsm
class SimImeiRetriever(SimGsm):
def __init__(self, port, logger):
SimGsm.__init__(self, port, logger)
def getIMEI(self):
self.logger.debug("retrieving IMEI")
data = self.commandAndStdResult("AT+GSN", 1000)
if data is None:
return None
return str(data).strip()

622
lib/sim900.bak/inetgsm.py Normal file
View File

@@ -0,0 +1,622 @@
#The MIT License (MIT)
#
#Copyright (c) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua )
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
"""
This file is part of sim-module package. Can be used for HTTP requests making.
sim-module package allows to communicate with SIM 900 modules: send SMS, make HTTP requests and use other
functions of SIM 900 modules.
Copyright (C) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua ) All Rights Reserved.
"""
from lib.sim900.gsm import *
class SimInetGSMConnection:
inetUnknown = -1
inetConnecting = 0
inetConnected = 1
inetClosing = 2
inetClosed = 3
class SimInetGSM(SimGsm):
def __init__(self, port, logger):
SimGsm.__init__(self, port, logger)
self.__ip = None
#user agent
self.__userAgent = "Aminis SIM-900 module client (version 0.1)"
self.__connectionState = SimInetGSMConnection.inetUnknown
self.__httpResult = 0
self.__httpResponse = None
@property
def connectionState(self):
return self.__connectionState
@property
def httpResult(self):
return self.__httpResult
@property
def httpResponse(self):
return self.__httpResponse
@property
def ip(self):
return self.__ip
@property
def userAgent(self):
return self.__userAgent
@userAgent.setter
def userAgent(self, value):
self.__userAgent = value
def checkGprsBearer(self, bearerNumber = 1):
"""
Checks GPRS connection. After calling of this method
:param bearerNumber: bearer number
:return: True if checking was without mistakes, otherwise returns False
"""
self.logger.debug("checking GPRS bearer connection")
ret = self.commandAndStdResult(
"AT+SAPBR=2,{0}".format(bearerNumber),
1000,
["OK"]
)
if (ret is None) or (self.lastResult != "OK"):
self.setError("{0}: error, lastResult={1}, ret={2}".format(inspect.stack()[0][3], self.lastResult, ret))
return False
ret = str(ret).strip()
self.logger.debug("{0}: result = {1}".format(inspect.stack()[0][3], ret))
response = str(ret).split(":")
if len(response) < 2:
self.setError("{0}:error, wrong response length, ret = {1}".format(inspect.stack()[0][3], ret))
return False
#parsing string like:
# +SAPBR: 1,1,"100.80.75.124" - when connected (channel 1)
# +SAPBR: 1,3,"0.0.0.0" - when disconnected (channel 1)
if response[0] != "+SAPBR":
self.setWarn("{0}: warning, response is not '+SAPBR', response = {1}".format(inspect.stack()[0][3], response[0]))
return False
response = splitAndFilter(response[1], ",")
self.logger.debug("{0}: sapbr result = \"{1}\"".format(inspect.stack()[0][3], response))
if len(response) < 3:
self.setError("{0}: wrong SAPBR result length, (sapbr result = '{1}')".format(inspect.stack()[0][3], response[1]))
return False
if response[0] != str(bearerNumber):
return
self.__ip = None
if response[1] == "0":
self.__connectionState = SimInetGSMConnection.inetConnecting
elif response[1] == "1":
self.__connectionState = SimInetGSMConnection.inetConnected
self.__ip = response[2].strip("\"").strip()
elif response[1] == "2":
self.__connectionState = SimInetGSMConnection.inetClosing
elif response[1] == "3":
self.__connectionState = SimInetGSMConnection.inetClosed
else:
self.__connectionState = SimInetGSMConnection.inetUnknown
return True
def attachGPRS(self, apn, user=None, password=None, bearerNumber = 1):
"""
Attaches GPRS connection for SIM module
:param apn: Access Point Name
:param user: User name (Login)
:param password: Password
:param bearerNumber: Bearer number
:return: True if everything was OK, otherwise returns False
"""
#checking current connection state
if not self.checkGprsBearer(bearerNumber):
return False
#going out if already connected
if self.connectionState == SimInetGSMConnection.inetConnected:
return True
#Closing the GPRS PDP context. We dont care of result
self.execSimpleOkCommand("AT+CIPSHUT", 500)
#initialization sequence for GPRS attaching
commands = [
["AT+SAPBR=3,{0},\"CONTYPE\",\"GPRS\"".format(bearerNumber), 1000 ],
["AT+SAPBR=3,{0},\"APN\",\"{1}\"".format(bearerNumber, apn), 500 ],
["AT+SAPBR=3,{0},\"USER\",\"{1}\"".format(bearerNumber, user), 500 ],
["AT+SAPBR=3,{0},\"PWD\",\"{1}\"".format(bearerNumber, password), 500 ],
["AT+SAPBR=1,{0}".format(bearerNumber), 10000 ]
]
#executing commands sequence
if not self.execSimpleCommandsList(commands):
return False
#returning GPRS checking sequence
return self.checkGprsBearer()
def disconnectTcp(self):
"""
Disconnects TCP connection
:return:
"""
return self.commandAndStdResult("AT+CIPCLOSE", 1000, ["OK"])
def dettachGPRS(self, bearerNumber = 1):
"""
Detaches GPRS connection
:param bearerNumber: bearer number
:return: True if de
"""
#Disconnecting TCP. Ignoring result
self.disconnectTcp()
#checking current GPRS connection state
if self.checkGprsBearer(bearerNumber):
if self.connectionState == SimInetGSMConnection.inetClosed:
return True
#disconnecting GPRS connection for given bearer number
return self.execSimpleOkCommand("AT+SAPBR=0,{0}".format(bearerNumber), 1000)
def terminateHttpRequest(self):
"""
Terminates current HTTP request.
:return: True if when operation processing was without errors, otherwise returns False
"""
return self.execSimpleOkCommand("AT+HTTPTERM", 500)
def __parseHttpResult(self, httpResult, bearerChannel = None):
"""
Parses http result string.
:param httpResult: string to parse
:param bearerChannel: bearer channel
:return: returns http result code and response length
"""
self.logger.debug("{0}: dataLine = {1}".format(inspect.stack()[0][3], httpResult))
response = splitAndFilter(httpResult, ":")
if len(response) < 2:
self.setWarn("{0}: wrong HTTP response length, length = {1}".format(inspect.stack()[0][3], len(response)))
return None
if response[0] != "+HTTPACTION":
self.setWarn("{0}: http response is not a '+HTTPACTION', response = '{1}'".format(inspect.stack()[0][3], response[0]))
return None
response = splitAndFilter(response[1], ",")
if len(response) < 3:
self.setWarn("{0}: wrong response length".format(inspect.stack()[0][3]))
return None
#checking bearer channel if necessary
if bearerChannel is not None:
if response[0] != str(bearerChannel):
self.setWarn("{0}: bad bearer number".format(inspect.stack()[0][3]))
return None
httpResultCode = str(response[1])
if not httpResultCode.isnumeric():
self.setWarn("{0}: response code is not numeric!".format(inspect.stack()[0][3]))
return None
httpResultCode = int(httpResultCode)
if httpResultCode != 200:
return [httpResultCode, 0]
responseLength = str(response[2])
if not responseLength.isnumeric():
self.setWarn("{0}: response length is not numeric".format(inspect.stack()[0][3]))
return False
return [httpResultCode, int(responseLength)]
def __readHttpResponse(self, httpMethodCode, responseLength):
"""
Reads http response data from SIM module buffer
:param httpMethodCode: ?
:param responseLength: response length
:return: True if reading was successful, otherwise returns false
"""
self.logger.debug("asking for http response (length = {0})".format(responseLength))
#trying to read HTTP response data
ret = self.commandAndStdResult(
"AT+HTTPREAD={0},{1}".format(httpMethodCode, responseLength),
10000,
["OK"]
)
if (ret is None) or (self.lastResult != "OK"):
self.setError("{0}: error reading http response data".format(inspect.stack()[0][3]))
return False
#removing leading \n symbols
#TODO: we must remove only 1 \n, not all! Fix it!
ret = str(ret).strip()
#reading first string in response (it must be "+HTTPREAD")
httpReadResultString = ""
while True:
if len(ret) == 0:
break
httpReadResultString += ret[0]
ret = ret[1:]
if "\n" in httpReadResultString:
break
httpReadResultString = str(httpReadResultString).strip()
if len(httpReadResultString) == 0:
self.setError("{0}: wrong http response. Result is empty".format(inspect.stack()[0][3]))
return False
httpReadResult = str(httpReadResultString).strip()
self.logger.debug("{0}: httpReadResult = {1}".format(inspect.stack()[0][3], httpReadResult))
httpReadResult = splitAndFilter(httpReadResult, ":")
if (len(httpReadResult) < 2) or (httpReadResult[0] != "+HTTPREAD"):
self.setError("{0}: bad response (cant find '+HTTPREAD'".format(inspect.stack()[0][3]))
return False
if int(httpReadResult[1]) != responseLength:
self.setWarn("{0}: bad response, wrong responseLength = {1}".format(inspect.stack()[0][3], responseLength))
return False
self.__httpResponse = ret
return True
@staticmethod
def ___isOkHttpResponseCode(code):
"""
Checks that given HTTP return code is successful result code
:param code: http result code for checking
:return: true if given code is HTTP operation successful
"""
return code in [200, 201, 202, 203, 204, 205, 206, 207, 226]
@staticmethod
def __isNoContentResponse(code):
"""
Checks that HTTP result code is 'NO CONTENT' result code
:param code: code for analysis
:return: true when code is 'NO CONTENT' code, otherwise returns false
"""
return code == 204
@staticmethod
def ___isHttpResponseCodeReturnsData(code):
"""
Checks that http operation returns data by given http result code
:param code: given http call result code
:return: true if http request must return data, otherwise returns false
"""
return code in [200, 206]
def httpGet(self, server, port = 80, path = "/", bearerChannel = 1):
"""
Makes HTTP GET request to the given server and script
:param server: server (host) address
:param port: http port
:param path: path to the script
:param bearerChannel: bearer channel number
:return: true if operation was successfully finished. Otherwise returns false
"""
self.__clearHttpResponse()
#TODO: close only when opened
self.terminateHttpRequest()
#HTTP GET request sequence
simpleCommands = [
[ "AT+HTTPINIT", 2000 ],
[ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ],
[ "AT+HTTPPARA=\"URL\",\"{0}:{2}{1}\"".format(server, path,port), 500 ],
[ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ],
[ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ],
[ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ],
[ "AT+HTTPACTION=0", 10000 ]
]
#executing http get sequence
if not self.execSimpleCommandsList(simpleCommands):
self.setError("error executing HTTP GET sequence")
return False
#reading HTTP request result
dataLine = self.readDataLine(10000)
if dataLine is None:
return False
#parsing string like this "+HTTPACTION:0,200,15"
httpResult = self.__parseHttpResult(dataLine, 0)
if httpResult is None:
return False
#assigning HTTP result code
self.__httpResult = httpResult[0]
#it's can be bad http code, let's check it
if not self.___isOkHttpResponseCode(self.httpResult):
self.terminateHttpRequest()
return True
#when no data from server we just want go out, everything if OK
if not self.___isHttpResponseCodeReturnsData(self.httpResult):
self.terminateHttpRequest()
return True
responseLength = httpResult[1]
if responseLength == 0:
self.terminateHttpRequest()
return True
self.logger.debug("reading http response data")
if not self.__readHttpResponse(0, responseLength):
return False
return True
def __clearHttpResponse(self):
self.__httpResponse = None
self.__httpResult = 0
def httpPOST(self, server, port, path, parameters, bearerChannel = 1, contentType="application/x-www-form-urlencoded"):
"""
Makes HTTP POST request to the given server and script
:param server: server (host) address
:param port: server port
:param path: path to the script
:param parameters: POST parameters
:param bearerChannel: bearer channel number
:return: true if operation was successfully finished. Otherwise returns false
"""
self.__clearHttpResponse()
#TODO: close only when opened
self.terminateHttpRequest()
#HTTP POST request commands sequence
simpleCommands = [
[ "AT+HTTPINIT", 2000 ],
[ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ],
[ "AT+HTTPPARA=\"URL\",\"{0}:{1}{2}\"".format(server, port, path), 500 ],
[ "AT+HTTPPARA=\"CONTENT\",\"{}\"".format(contentType), 500 ],
#[ "AT+HTTPPARA=\"CONTENT\",\"{}\"".format(""), 500 ],
[ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ],
[ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ],
[ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ]
]
#executing commands sequence
if not self.execSimpleCommandsList(simpleCommands):
return False
#uploading data
self.logger.debug("uploading HTTP POST data")
ret = self.commandAndStdResult(
"AT+HTTPDATA={0},10000".format(len(parameters)),
7000,
["DOWNLOAD", "ERROR"]
)
if (ret is None) or (self.lastResult != "DOWNLOAD"):
self.setError("{0}: can't upload HTTP POST data 1".format(inspect.stack()[0][3]))
return False
self.simpleWriteLn(parameters)
dataLine = self.readDataLine(500)
if (dataLine is None) or (dataLine != "OK"):
self.setError("{0}: can't upload HTTP POST data".format(inspect.stack()[0][3]))
return
self.logger.debug("actually making request")
#TODO: check CPU utilization
if not self.execSimpleOkCommand("AT+HTTPACTION=1", 15000):
return False
#reading HTTP request result
dataLine = self.readDataLine(15000)
if dataLine is None:
self.setError("{0}: empty HTTP request result string".format(inspect.stack()[0][3]))
return False
#parsing string like this "+HTTPACTION:0,200,15"
httpResult = self.__parseHttpResult(dataLine, bearerChannel)
if httpResult is None:
return False
#assigning HTTP result code
self.__httpResult = httpResult[0]
#it's can be bad http code, let's check it
if not self.___isOkHttpResponseCode(self.httpResult):
self.terminateHttpRequest()
return True
#when no data from server we just want go out, everything if OK
if (
(self.__isNoContentResponse(self.httpResult)) or
(not self.___isHttpResponseCodeReturnsData(self.httpResult))
):
self.terminateHttpRequest()
return True
responseLength = httpResult[1]
if responseLength == 0:
self.terminateHttpRequest()
return True
self.logger.debug("reading http request response data")
if not self.__readHttpResponse(0, responseLength):
return False
return True
# self.disconnectTcp()
#
# return True
def httpPOST_DATA(self, server, port, path, parameters, bearerChannel = 1):
"""
Makes HTTP POST request to the given server and script
:param server: server (host) address
:param port: server port
:param path: path to the script
:param parameters: POST parameters
:param bearerChannel: bearer channel number
:return: true if operation was successfully finished. Otherwise returns false
"""
self.__clearHttpResponse()
#TODO: close only when opened
self.terminateHttpRequest()
#HTTP POST request commands sequence
simpleCommands = [
[ "AT+HTTPINIT", 2000 ],
[ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ],
[ "AT+HTTPPARA=\"URL\",\"{0}:{1}{2}\"".format(server, port, path), 500 ],
[ "AT+HTTPPARA=\"CONTENT\",\"multipart/form-data\"", 500 ],
[ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ],
[ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ],
[ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ]
]
#executing commands sequence
if not self.execSimpleCommandsList(simpleCommands):
return False
#uploading data
self.logger.debug("uploading HTTP POST data")
ret = self.commandAndStdResult(
"AT+HTTPDATA={0},10000".format(len(parameters)),
7000,
["DOWNLOAD", "ERROR"]
)
if (ret is None) or (self.lastResult != "DOWNLOAD"):
self.setError("{0}: can't upload HTTP POST data".format(inspect.stack()[0][3]))
return False
self.simpleWriteLn(parameters)
dataLine = self.readDataLine(500)
if (dataLine is None) or (dataLine != "OK"):
self.setError("{0}: can't upload HTTP POST data".format(inspect.stack()[0][3]))
return
self.logger.debug("actually making request")
#TODO: check CPU utilization
if not self.execSimpleOkCommand("AT+HTTPACTION=1", 15000):
return False
#reading HTTP request result
dataLine = self.readDataLine(15000)
if dataLine is None:
self.setError("{0}: empty HTTP request result string".format(inspect.stack()[0][3]))
return False
#parsing string like this "+HTTPACTION:0,200,15"
httpResult = self.__parseHttpResult(dataLine, bearerChannel)
if httpResult is None:
return False
#assigning HTTP result code
self.__httpResult = httpResult[0]
#it's can be bad http code, let's check it
if not self.___isOkHttpResponseCode(self.httpResult):
self.terminateHttpRequest()
return True
#when no data from server we just want go out, everything if OK
if (
(self.__isNoContentResponse(self.httpResult)) or
(not self.___isHttpResponseCodeReturnsData(self.httpResult))
):
self.terminateHttpRequest()
return True
responseLength = httpResult[1]
if responseLength == 0:
self.terminateHttpRequest()
return True
self.logger.debug("reading http request response data")
if not self.__readHttpResponse(0, responseLength):
return False
return True
# self.disconnectTcp()
#
# return True
#
# int res= gsm.read(result, resultlength);
# //gsm.disconnectTCP();
# return res;

View File

@@ -0,0 +1,62 @@
#The MIT License (MIT)
#
#Copyright (c) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua )
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
"""
This file is part of sim-module package. Shared functions for sim-module package.
sim-module package allows to communicate with SIM 900 modules: send SMS, make HTTP requests and use other
functions of SIM 900 modules.
Copyright (C) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua ) All Rights Reserved.
"""
import os
import logging
import inspect
### conditional import ###
# our company uses big file amshared.py which is not needed for this library. so here we will import only needful
# functions
if os.path.exists(os.path.abspath(os.path.join(os.path.dirname(__file__), "__stand_alone__.py"))):
from lib.sim900.amsharedmini import *
else:
from lib.amshared import *
class AminisLastErrorHolderWithLogging(AminisLastErrorHolder):
def __init__(self, logger = None):
AminisLastErrorHolder.__init__(self)
self.logger = logger
if self.logger is None:
self.logger = logging.getLogger(__name__)
def setError(self, value):
AminisLastErrorHolder.setError(self, value)
self.logger.error(value)
def setWarn(self, value):
AminisLastErrorHolder.setError(self, value)
self.logger.warn(value)
def noneToEmptyString(value):
return '' if value is None else value

View File

@@ -0,0 +1,653 @@
#The MIT License (MIT)
#
#Copyright (c) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua )
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
"""
This file is part of sim-module package. SMS processing classes and functions.
sim-module package allows to communicate with SIM 900 modules: send SMS, make HTTP requests and use other
functions of SIM 900 modules.
Copyright (C) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua ) All Rights Reserved.
"""
from lib.sim900.gsm import SimGsm
from lib.sim900.simshared import *
import binascii
import random
class SimSmsPduCompiler(AminisLastErrorHolder):
def __init__(self, smsCenterNumber="", targetPhoneNumber="", smsTextMessage=""):
AminisLastErrorHolder.__init__(self)
#sms center number
self.__smsCenterNumber = self.__preprocessPhoneNumber(smsCenterNumber)
#sms recipient number
self.__smsRecipientNumber = self.__preprocessPhoneNumber(targetPhoneNumber)
#sms text
self.smsText = smsTextMessage
self.flashMessage = False
#validation period for message
self.__validationPeriod = None
def clear(self):
"""
Clears all internal buffers
:return: nothing
"""
self.clear()
self.__smsCenterNumber = ""
self.__smsRecipientNumber = ""
self.smsText = ""
self.flashMessage = False
self.__validationPeriod = None
@property
def smsCenterNumber(self):
"""
SMS center number
:return: returns SMS center number
"""
return self.__smsCenterNumber
@staticmethod
def __preprocessPhoneNumber(value):
value = noneToEmptyString(value)
value = str(value).strip()
value = value.replace(" ", "")
return value.replace("\t", "")
@smsCenterNumber.setter
def smsCenterNumber(self, value):
"""
Sets SMS center number
:param value: new SMS center number
:return: nothing
"""
self.__smsCenterNumber = self.__preprocessPhoneNumber(value)
@property
def smsRecipientNumber(self):
"""
Returns SMS recipient number
:return: SMS recipient number
"""
return self.__smsRecipientNumber
@smsRecipientNumber.setter
def smsRecipientNumber(self, value):
"""
Sets SMS recipient number
:param value: SMS recipient number
:return: nothig
"""
self.__smsRecipientNumber = self.__preprocessPhoneNumber(value)
@staticmethod
def __clientPhoneNumberLength(number):
"""
Returns phone number without '+' symbol and without padding 'F' at end
:param number: number for length calculation
:return: number length
"""
num = str(number).strip()
num = num.replace("+", "")
return len(num)
@staticmethod
def __encodePhoneNumber(number):
"""
Encodes phone number according to PDU rules
:param number: phone number for encoding
:return: encoded phone number
"""
num = str(number).strip()
num = num.replace("+", "")
#adding pad byte
if (len(num) % 2) != 0:
num += 'F'
#calculating reverted result, according to the
result = ""
i = 0
while i < len(num):
result += num[i+1] + num[i]
i += 2
return result
def __compileScaPart(self):
"""
Compiles SCA part of PDU request.
:return: compiled request
"""
if len(self.smsCenterNumber) == 0:
return "00"
smsCenterNumber = SimSmsPduCompiler.__encodePhoneNumber(self.smsCenterNumber)
sca = SimSmsPduCompiler.__byteToHex ( ((len(smsCenterNumber) // 2) + 1)) + "91" + smsCenterNumber
return sca
def __canUse7BitsEncoding(self, text = None):
"""
Checks that message can be encoded in 7 bits.
:param text: optional argument - text for checking, when not specified whole sms text will be checked
:return: true when text can be encoded in 7 bits, otherwise returns false
"""
if text is None:
return all(ord(c) < 128 for c in self.smsText)
return all(ord(c) < 128 for c in text)
@staticmethod
def __encodeMessageIn7Bits(text):
"""
Encodes ASCII text message block with 7 bit's encoding. So, each 8 symbols of message will be encoded in 7 bytes
:param text: text for encoding
:return: 7-bit encoded message
"""
data = bytearray(text.encode("ascii"))
#encoding
i = 1
while i < len(data):
j = len(data) - 1
while j>=i:
firstBit = 0x80 if ((data[j] % 2) > 0) else 0x00
data[j-1] = (data[j-1] & 0x7f) | firstBit
data[j] = data[j] >> 1
j -= 1
i += 1
#looking for first 0x00 byte
index = 0
for b in data:
if b == 0x00:
break
index += 1
data = data[:index]
# 'hellohello' must be encoded as "E8329BFD4697D9EC37"
return binascii.hexlify(data).decode("ascii").upper()
def __encodeMessageAsUcs2(self, text):
"""
Encodes message with UCS2 encoding
:param text: text for encoding
:return: UCS2 encoded message
"""
try:
d = binascii.hexlify(text.encode("utf-16-be"))
return d.decode("ascii").upper()
except Exception as e:
self.setError("error encoding text: {0}".format(e))
return None
def __compilePduTypePart(self, isMultupartMessage):
"""
Returns PDU Type part.
:param isMultupartMessage: must be true when message is multupart
:return: encoded PDU-Type
"""
#returning PDU-Type when validation period is not specified
if self.__validationPeriod is None:
if not isMultupartMessage:
return "01"
return "41"
#special value when multi-part message
if isMultupartMessage:
return "51"
return "11"
def __compilePduTpVpPart(self):
"""
Returns TP-VP part (validity period for SMS)
:return:
"""
# TP- VP — TP-Validity-Period/ "AA" means 4 days. Note: This octet is optional, see bits 4 and 3 of the first octet
return self.__validationPeriod
def setValidationPeriodInMinutes(self, value):
"""
Set message validation period in minutes interval. Up to 12 hours.
:param value: minutes count
:return: true if everything is OK, otherwise returns false
"""
#0-143 (TP-VP + 1) x 5 minutes 5, 10, 15 minutes ... 11:55, 12:00 hours
count = value // 5
if count > 143:
self.setError("Wrong interval, must be between 1 and 720 minutes")
return False
self.__validationPeriod = self.__byteToHex(count)
return True
def setValidationPeriodInHours(self, value):
"""
Set validation period in hours (up to 24 hours) with 0.5 hour step
:param value: hours count (float), must be >= 12 and <= 24
:return: true if everything is OK, otherwise returns false
"""
#144-167 (12 + (TP-VP - 143) / 2 ) hours 12:30, 13:00, ... 23:30, 24:00 hours
if (value < 12) or (value > 24):
self.setError("Value must be between 12 and 24 hours")
return False
value = value - 12
count = int(value)
if (value - count) >= 0.5:
count = count*2 + 1
else:
count = count*2
if count>23:
count = 23
self.__validationPeriod = self.__byteToHex(count + 144)
return True
def setValidationPeriodInDays(self, value):
"""
Can set message validation period in days (2-30 days)
:param value: days count (must be >=2 and <=30)
:return: true when value is OK, otherwise returns false
"""
#168-196 (TP-VP - 166) days 2, 3, 4, ... 30 days
if (value < 2) or (value > 30):
self.setError("Bad interval, value must be >= 2 days and <= 30 days")
return False
self.__validationPeriod = self.__byteToHex(value + 166)
return True
def setValidationPeriodInWeeks(self, value):
"""
Set validation period in weeks (from 5 to 63 weeks)
:param value: weeks count (must be >=5 and <= 63)
:return: true if everything is OK, otherwise returns false
"""
# 197-255 (TP-VP - 192) weeks 5, 6, 7, ... 63 weeks
if (value < 5) or (value > 63):
self.setError("Wrong value, value must be >= 5 and <= 63 weeks")
return False
value = value - 5
self.__validationPeriod = self.__byteToHex(value + 197)
return True
def __compileTpdu(self, pieceNumber, totalPiecesCount, pieceText, messageId = None):
"""
Compiles TPDU part of PDU message request.
:return: compiled TPDU
"""
# TPDU = "PDU-Type" + "TP-MR" + "TP-DA" + "TP-PID" + "TP-DCS" + "TP-VP" + "TP-UDL" + "TP-UD"
# PDU-Type is the same as SMS-SUBMIT-PDU
ret = ""
#checking that message have more than one part
isMultipartMessage = totalPiecesCount > 1
#adding PDU-Type
ret += self.__compilePduTypePart(isMultipartMessage)
#adding TP-MR (TP-Message-Reference).
ret += self.__byteToHex(pieceNumber+100)
# if totalPiecesCount > 1:
# #setting message reference manually
# ret += self.__byteToHex(pieceNumber)
# else:
# #The "00" value here lets the phone set the message reference number itself.
# ret += "00"
#encoding TP-DA (TP-Destination-Address - recipient address)
ret += self.__byteToHex(self.__clientPhoneNumberLength(self.smsRecipientNumber)) + "91" + self.__encodePhoneNumber(self.smsRecipientNumber)
#adding TP-PID (TP-Protocol ID)
ret += "00"
#adding TP-DCS (TP-Data-Coding-Scheme)
#00h: 7-bit encoding (160 symbols [after packing], but only ASCII)
#08h: UCS2 encoding (Unicode), 70 symbols, 2 bytes per symbol
#If first octet is "1" message will not be saved in mobile but only flashed on the screen
#10h: Flash-message with 7-bit encoding
#18h: Flash-message with UCS2 encoding
#checking that message CAN be encoded in 7 bits encoding
canBe7BitsEncoded = self.__canUse7BitsEncoding()
if canBe7BitsEncoded:
tpDcs = "00"
else:
tpDcs = "08"
if self.flashMessage:
tpDcs[0] = "1"
ret += tpDcs
#adding TP-VP (TP-Validity-Period) is it's specified
if self.__validationPeriod is not None:
ret += self.__compilePduTpVpPart()
#encoding message (7-bit or UCS2)
if canBe7BitsEncoded:
encodedMessage = self.__encodeMessageIn7Bits(pieceText)
else:
encodedMessage = self.__encodeMessageAsUcs2(pieceText)
#checking that message was encoded correctly
if encodedMessage is None:
self.setError("error encoding message: {0}".format(self.errorText))
return None
#adding TP-UDL (TP-User-Data-Length - message length)
if not isMultipartMessage:
if canBe7BitsEncoded:
#adding TEXT LENGTH IN SYMBOLS
ret += self.__byteToHex(len(self.smsText))
else:
ret += self.__byteToHex(len(encodedMessage)//2)
else:
if canBe7BitsEncoded:
ret += self.__byteToHex(len(pieceText) + 8)
else:
ret += self.__byteToHex(len(encodedMessage)//2 + 6)
#adding UDHL + UDH for multipart messages
if isMultipartMessage:
if canBe7BitsEncoded:
#length of UDH
udhl = bytearray([0x06])
#UDI IED entry type
iei = bytearray([0x08])
#length of UDH IED
iedl = bytearray([0x04])
# messageId
ied1Lo = messageId & 0xff
ied1Hi = ((messageId & 0xff00) >> 8)
ied1 = bytearray([ied1Hi, ied1Lo])
#total pieces count
ied2 = bytearray([totalPiecesCount])
#piece number
ied3 = bytearray([pieceNumber])
#compiling IED
ied = ied1 + ied2 + ied3
#compiling UDH
udh = iei + iedl + ied
else:
#length of UDH
udhl = bytearray([0x05])
#UDI IED entry type
iei = bytearray([0x00])
#length of UDH IED
iedl = bytearray([0x03])
#message id
ied1Lo = messageId & 0xff
ied1 = bytearray([ied1Lo])
#total pieces count
ied2 = bytearray([totalPiecesCount])
#piece number
ied3 = bytearray([pieceNumber])
#compiling IED
ied = ied1 + ied2 + ied3
#compiling UDH
udh = iei + iedl + ied
cudh = binascii.hexlify(udhl + udh).decode("ascii").upper()
print("cudh = '{0}'".format(cudh))
ret += cudh
#adding TP-UD (TP-User-Data - SMS message encoded as described in TP-DCS)
ret += encodedMessage
return ret
def messagesCount(self):
if self.__canUse7BitsEncoding():
symbolsCount = len(self.smsText)
if symbolsCount <= 160:
return 1
messagesCount = symbolsCount // 152
if symbolsCount % 152:
messagesCount += 1
return messagesCount
else:
symbolsCount = len(self.smsText)
if symbolsCount <= 70:
return 1
messagesCount = symbolsCount // 67
if symbolsCount % 67:
messagesCount += 1
return messagesCount
@staticmethod
def __byteToHex(value):
"""
Returns two-symbold hex-string representation of byte.
:param value: byte for encoding
:return: encoded value
"""
return "{:02X}".format(value)
def compile(self):
"""
Compiles PDU request (SCA + TPDU)
:return: SMS request in PDU format
"""
ret = []
symbolsCount = len(self.smsText)
msgCount = self.messagesCount()
isUcs2 = not self.__canUse7BitsEncoding()
if isUcs2:
symbolsInPiece = 67
else:
symbolsInPiece = 152
#generating message id for multi-part messages
messageId = None
if msgCount > 1:
messageId = random.randint(0, 65535)
for i in range(msgCount):
if msgCount == 1:
textPiece = self.smsText
else:
minIndex = i * symbolsInPiece
maxIndex = (minIndex + symbolsInPiece) if (minIndex + symbolsInPiece) < symbolsCount else (symbolsCount)
textPiece = self.smsText[minIndex : maxIndex]
ret += [(self.__compileScaPart(), self.__compileTpdu(i+1, msgCount, textPiece, messageId),)]
return ret
class SimGsmSmsHandler(SimGsm):
def __init__(self, port, logger):
SimGsm.__init__(self, port, logger)
self.sendingResult = ""
def clear(self):
SimGsm.clearError(self)
self.sendingResult = ""
def sendSms(self, phoneNumber, messageText, numberOfAttempts = 3):
tuneCommands = [
["AT+CMGS=?", 300], #checking that sms supported
["AT+CMGF=1", 1000]
]
self.logger.debug("initializing SIM module for SMS sending")
for cmd in tuneCommands:
if not self.execSimpleOkCommand(commandText=cmd[0],timeout=cmd[1]):
return False
for i in range(numberOfAttempts):
ret = self.commandAndStdResult(
"AT+CMGS=\"{0}\"".format(phoneNumber),
1000,
[">"]
)
if (ret is None) or (self.lastResult != ">"):
continue
ret = self.commandAndStdResult(
"{0}\n\x1a".format(messageText),
10000,
["ERROR", "OK"]
)
if (ret is None) or (self.lastResult not in ["OK"]):
continue
return True
self.setError("error sending sms...")
return False
def __sendPduMessageLow(self, sca, pdu, numberOfAttempts = 3):
tuneCommands = [
["AT+CSCS=\"GSM\"", 500],
# ["AT+CMGS?", 500], #checking that sms supported
["AT+CMGF=0", 1000]
]
self.logger.debug("initializing SIM module for SMS sending in PDU mode")
for cmd in tuneCommands:
if not self.execSimpleOkCommand(commandText=cmd[0], timeout=cmd[1]):
self.setError("error tuning module for sms sending")
return False
for i in range(numberOfAttempts):
ret = self.commandAndStdResult(
"AT+CMGS={0}".format(len(pdu) // 2),
1000,
[">"]
)
if (ret is None) or (self.lastResult != ">"):
continue
ret = self.commandAndStdResult(
"{0}\x1a".format(sca + pdu),
10000,
["ERROR", "OK"]
)
if (ret is None) or (self.lastResult != "OK"):
continue
self.sendingResult = ret.strip()
return True
return False
def sendPduMessage(self, pduHelper, numberOfAttempts = 3):
d = pduHelper.compile()
if d is None:
self.setError("error compiling PDU sms")
return False
piece = 1
for (sca, pdu,) in d:
self.logger.info("sendSms(): sca + pdu = \"{0}\"".format(sca + pdu))
if not self.__sendPduMessageLow(sca, pdu, numberOfAttempts):
return False
self.logger.info("Sending result = {0}".format(self.sendingResult))
return True

View File

@@ -0,0 +1,126 @@
#The MIT License (MIT)
#
#Copyright (c) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua )
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
"""
This file is part of sim-module package. USSD requests processing classes and functions.
sim-module package allows to communicate with SIM 900 modules: send SMS, make HTTP requests and use other
functions of SIM 900 modules.
Copyright (C) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua ) All Rights Reserved.
"""
from lib.sim900.gsm import SimGsm
from lib.sim900.simshared import *
class SimUssdHandler(SimGsm):
def __init__(self, port, logger):
SimGsm.__init__(self, port, logger)
self.lastUssdResult = None
@staticmethod
def __parseResult(value):
#parsing strings like '+CUSD: 0,"data string"'
#searching and removing '+CUSD' prefix
idx = value.find(":")
if idx == -1:
return None
left = value[:idx]
left = str(left).strip()
if left != "+CUSD":
return None
data = value[(idx+1):]
data = str(data).strip()
#searching and removing numeric parameter
idx = data.find(",")
if idx == -1:
return None
#also, we can use this code. But I dont know how
code = data[:idx]
data = data[(idx+1):]
data = str(data).strip()
data = data.rstrip(',')
data = data.strip('"')
return data
def runUssdCode(self, ussdCode):
cmd = "AT+CUSD=1,\"{0}\",15".format(ussdCode)
self.logger.info("running command = '{0}'".format(cmd))
#executing command, also we can retrieve result right here
result = self.commandAndStdResult(cmd, 20000)
if (result is None) or (self.lastResult != 'OK'):
self.setWarn("error running USSD command '{0}'".format(ussdCode))
return False
result = str(result).strip()
#checking that we have result here
if len(result) > 0:
self.lastUssdResult = self.__parseResult(result)
if self.lastUssdResult is None:
self.setWarn("error parsing USSD command result")
return False
return True
#reading data line
dataLine = self.readNullTerminatedLn(20000)
if dataLine is None:
self.setWarn("error waiting for USSD command result")
return False
dataLine = str(dataLine).strip()
#reading bytes in the end of response
data = self.readFixedSzieByteArray(1, 500)
if data == bytes([0xff]):
data = None
endLine = self.readLn(500)
if (data is not None) or (endLine is not None):
endLine = noneToEmptyString(data) + noneToEmptyString(endLine)
endLine = str(endLine).strip()
if len(endLine) > 0:
dataLine += endLine
#parsing CUSD result
self.lastUssdResult = self.__parseResult(dataLine)
if self.lastUssdResult is None:
return False
return True

View File

@@ -33,7 +33,6 @@ import time
import serial import serial
import logging import logging
from lib.sim900.simshared import * from lib.sim900.simshared import *
import struct
class GsmSpecialCharacters: class GsmSpecialCharacters:
ctrlz = 26 #//Ascii character for ctr+z. End of a SMS. ctrlz = 26 #//Ascii character for ctr+z. End of a SMS.
@@ -133,7 +132,7 @@ class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging):
""" """
return self.print(commandLine, encoding) return self.print(commandLine, encoding)
def printLn(self, commandString, bytes=None, encoding = "ascii"): def printLn(self, commandString, encoding = "ascii"):
""" """
Sends string data and CR/LF in the end to the SIM module Sends string data and CR/LF in the end to the SIM module
@@ -141,16 +140,33 @@ class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging):
:param encoding: before sending string it will be converted to the bytearray with this encoding :param encoding: before sending string it will be converted to the bytearray with this encoding
:return: True if data sent, otherwise returns False :return: True if data sent, otherwise returns False
""" """
if bytes is not None: if not isinstance(commandString, bytes):
data = bytearray(commandString, encoding) + bytes + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
else:
data = bytearray(commandString, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf]) data = bytearray(commandString, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
else:
# print("Print Line data: {}".format(data)) data = commandString + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
return self.__sendRawBytes(data) return self.__sendRawBytes(data)
def simpleWriteLn(self, commandLine, bytes=None, encoding = "ascii"): def simpleWriteLns(self, commandLines, encoding = "ascii"):
for i, l in enumerate(commandLines):
if i < len(commandLines)-1:
if not isinstance(l, bytes):
data = bytearray(l, encoding)
else:
data = l
else:
if not isinstance(l, bytes):
data = bytearray(l, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
else:
data = l + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
r = self.__sendRawBytes(data)
if r is False:
return r
return r
def simpleWriteLn(self, commandLine, encoding = "ascii"):
""" """
Just alias for printLn() method Just alias for printLn() method
@@ -159,7 +175,7 @@ class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging):
:return: True if data sent, otherwise returns False :return: True if data sent, otherwise returns False
""" """
return self.printLn(commandLine, encoding=encoding, bytes=bytes) return self.printLn(commandLine, encoding)
def flushInput(self): def flushInput(self):
""" """

View File

@@ -38,6 +38,10 @@ class SimInetGSMConnection:
inetClosing = 2 inetClosing = 2
inetClosed = 3 inetClosed = 3
def chunks(l, n):
n = max(1, n)
return [l[i:i + n] for i in range(0, len(l), n)]
class SimInetGSM(SimGsm): class SimInetGSM(SimGsm):
def __init__(self, port, logger): def __init__(self, port, logger):
SimGsm.__init__(self, port, logger) SimGsm.__init__(self, port, logger)
@@ -172,6 +176,47 @@ class SimInetGSM(SimGsm):
#returning GPRS checking sequence #returning GPRS checking sequence
return self.checkGprsBearer() return self.checkGprsBearer()
def attachCIP(self, apn, user=None, password=None, bearerNumber = 1):
"""
Attaches GPRS connection for SIM module
:param apn: Access Point Name
:param user: User name (Login)
:param password: Password
:param bearerNumber: Bearer number
:return: True if everything was OK, otherwise returns False
"""
#checking current connection state
# if not self.checkGprsBearer(bearerNumber):
# return False
#
# #going out if already connected
# if self.connectionState == SimInetGSMConnection.inetConnected:
# return True
#Closing the GPRS PDP context. We dont care of result
self.execSimpleOkCommand("AT+CIPSHUT", 500)
#initialization sequence for GPRS attaching
commands = [
["AT+CGATT=1", 1000 ],
["AT+CSNS=4,", 500 ],
["AT+CSTT=\"{}\",\"{}\",\"{}\"".format(apn, user, password), 500 ],
["AT+CIICR", 500 ],
["AT+CIFSR", 10000 ]
# ["AT+CIPSTART=\"{}\",\"{}\",\"{}\"".format("TCP", {})]
]
#executing commands sequence
if not self.execSimpleCommandsList(commands):
return False
return 1
#returning GPRS checking sequence
# return self.checkGprsBearer()
def disconnectTcp(self): def disconnectTcp(self):
""" """
Disconnects TCP connection Disconnects TCP connection
@@ -409,7 +454,7 @@ class SimInetGSM(SimGsm):
self.__httpResponse = None self.__httpResponse = None
self.__httpResult = 0 self.__httpResult = 0
def httpPOST(self, server, port, path, parameters, bearerChannel = 1): def httpPOST(self, server, port, path, parameters, contentType="application/x-www-form-urlencoded", bearerChannel = 1):
""" """
Makes HTTP POST request to the given server and script Makes HTTP POST request to the given server and script
@@ -431,10 +476,10 @@ class SimInetGSM(SimGsm):
[ "AT+HTTPINIT", 2000 ], [ "AT+HTTPINIT", 2000 ],
[ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ], [ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ],
[ "AT+HTTPPARA=\"URL\",\"{0}:{1}{2}\"".format(server, port, path), 500 ], [ "AT+HTTPPARA=\"URL\",\"{0}:{1}{2}\"".format(server, port, path), 500 ],
[ "AT+HTTPPARA=\"CONTENT\",\"application/x-www-form-urlencoded\"", 500 ], [ "AT+HTTPPARA=\"CONTENT\",\"{0}\"".format(contentType), 500 ],
[ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ], [ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ],
[ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ], [ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ],
[ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ] [ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ],
] ]
#executing commands sequence #executing commands sequence
@@ -445,7 +490,7 @@ class SimInetGSM(SimGsm):
#uploading data #uploading data
self.logger.debug("uploading HTTP POST data") self.logger.debug("uploading HTTP POST data")
ret = self.commandAndStdResult( ret = self.commandAndStdResult(
"AT+HTTPDATA={0},10000".format(len(parameters)), "AT+HTTPDATA={0},60000".format(len(parameters)),
7000, 7000,
["DOWNLOAD", "ERROR"] ["DOWNLOAD", "ERROR"]
) )
@@ -456,9 +501,14 @@ class SimInetGSM(SimGsm):
self.simpleWriteLn(parameters) self.simpleWriteLn(parameters)
dataLine = self.readDataLine(500) # if len(parameters)<32:
# self.simpleWriteLn(parameters)
# else:
# parameters = self.simpleWriteLns(chunks(parameters,32))
dataLine = self.readDataLine(maxWaitTime=20000)
if (dataLine is None) or (dataLine != "OK"): if (dataLine is None) or (dataLine != "OK"):
self.setError("{0}: can't upload HTTP POST data".format(inspect.stack()[0][3])) self.setError("{0}: can't upload HTTP POST data {1}".format(inspect.stack()[0][3], dataLine))
return return
self.logger.debug("actually making request") self.logger.debug("actually making request")
@@ -512,7 +562,12 @@ class SimInetGSM(SimGsm):
# #
# return True # return True
def httpPOST_DATA(self, server, port, path, parameters, bearerChannel = 1): #
# int res= gsm.read(result, resultlength);
# //gsm.disconnectTCP();
# return res;
def httpPOST_CIP(self, server, port, path, parameters, contentType="application/x-www-form-urlencoded", bearerChannel = 1):
""" """
Makes HTTP POST request to the given server and script Makes HTTP POST request to the given server and script
@@ -524,39 +579,29 @@ class SimInetGSM(SimGsm):
:return: true if operation was successfully finished. Otherwise returns false :return: true if operation was successfully finished. Otherwise returns false
""" """
# self.__clearHttpResponse() self.__clearHttpResponse()
#TODO: close only when opened #TODO: close only when opened
# self.terminateHttpRequest() self.terminateHttpRequest()
#HTTP POST request commands sequence #HTTP POST request commands sequence
# simpleCommands = [
# [ "AT+HTTPINIT", 2000 ],
# [ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ],
# [ "AT+HTTPPARA=\"URL\",\"{0}:{1}{2}\"".format(server, port, path), 500 ],
# [ "AT+HTTPPARA=\"CONTENT\",\"multipart/form-data\"", 500 ],
# [ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ],
# [ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ],
# [ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ]
# ]
simpleCommands = [ simpleCommands = [
[ "AT+CGATT?", 2000 ], # ["AT+CGATT=1", 1000 ],
[ "AT+CIPCLOSE", 1000 ], # ["AT+CSNS=4,", 500 ],
# [ "AT+CIPMUX=0", 1000 ], # ["AT+CSTT=\"{}\",\"{}\",\"{}\"".format("wholesale", "", ""), 500 ],
[ "AT+CSTT=\"{0}\"".format('wholesale'), 1000], # ["AT+CIICR", 500 ],
[ "AT+CIICR", 500 ], # ["AT+CIFSR", 10000 ]
[ "AT+CIFSR", 500 ], [ "AT+CIPSTART=\"TCP\",\"{0}{2}\",\"{1}\"".format(server, port, path), 500 ],
[ "AT+CIPSTART=\"{0}\",\"{1}\",\"{2}\"".format("TCP", server, port), 500 ], [ "AT+CIPSEND=", 500 ],
[ "AT+CIPSEND", 500 ],
# [ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ]
] ]
#executing commands sequence #executing commands sequence
if not self.execSimpleCommandsList(simpleCommands): if not self.execSimpleCommandsList(simpleCommands):
return False return False
#uploading data #uploading data
# self.logger.debug("uploading HTTP POST data") self.logger.debug("uploading HTTP POST data")
# ret = self.commandAndStdResult( # ret = self.commandAndStdResult(
# "AT+HTTPDATA={0},10000".format(len(parameters)), # "AT+HTTPDATA={0},10000".format(len(parameters)),
# 7000, # 7000,
@@ -574,11 +619,11 @@ class SimInetGSM(SimGsm):
self.setError("{0}: can't upload HTTP POST data".format(inspect.stack()[0][3])) self.setError("{0}: can't upload HTTP POST data".format(inspect.stack()[0][3]))
return return
self.logger.debug("actually making request") # self.logger.debug("actually making request")
#TODO: check CPU utilization # #TODO: check CPU utilization
if not self.execSimpleOkCommand("AT+HTTPACTION=1", 15000): # if not self.execSimpleOkCommand("AT+HTTPACTION=1", 15000):
return False # return False
#reading HTTP request result #reading HTTP request result
dataLine = self.readDataLine(15000) dataLine = self.readDataLine(15000)
@@ -625,7 +670,6 @@ class SimInetGSM(SimGsm):
# #
# return True # return True
# #
# int res= gsm.read(result, resultlength); # int res= gsm.read(result, resultlength);
# //gsm.disconnectTCP(); # //gsm.disconnectTCP();

52
logging.ini Normal file
View File

@@ -0,0 +1,52 @@
[loggers]
keys=root,datahandling,instruments
[handlers]
keys=consoleHandler, rootHandler, instrumentsHandler, datahandlingHandler
[formatters]
keys=myFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler, rootHandler
[logger_datahandling]
level=INFO
handlers=datahandlingHandler
qualname=datahandling
[logger_instruments]
level=INFO
handlers=instrumentsHandler
qualname=instruments
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=myFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
formatter=myFormatter
args=("config.log",)
[handler_rootHandler]
class=FileHandler
formatter=myFormatter
args=("main_log.txt",)
[handler_instrumentsHandler]
class=FileHandler
formatter=myFormatter
args=("instruments_log.txt",)
[handler_datahandlingHandler]
class=FileHandler
formatter=myFormatter
args=("datahandling_log.txt",)
[formatter_myFormatter]
format=[%(asctime)-15s] [%(MISSION_TIME)-10s] %(MISSION_ID)-10s %(name)-25s %(levelname)-8s %(message)s
datefmt=%Y-%m-%d %H:%M:%S

212
main.py
View File

@@ -1,116 +1,174 @@
__author__ = 'asc' __author__ = 'asc'
DEBUG = True DEBUG = True
import logging import logging
import logging.handlers,logging.config
if DEBUG: from instruments import Barometer, Camera
from instruments import Barometer_Debug2 as Barometer from datahandling import Datalogger, Datareporter
from instruments import Camera_Debug2 as Camera from system import System as System
from datahandling import Datalogger_Debug as Datalogger from threading import Timer
from datahandling import Datareporter_Debug2 as Datareporter from datahandling import Record
from system import System_Debug as System from mission import Mission
import threading import random
import datetime import string
import configparser # class ContextFilter(logging.Filter):
# """
# This is a filter which injects contextual information into the log.
# """
# def filter(self, record):
# record.MYVAR = MYVAR
# return True
# logging.basicConfig(level=logging.DEBUG, format=("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) logging.config.fileConfig('logging.ini')
# log = logging.getLogger("instruments")
# log.setLevel(logging.DEBUG)
# log.
# # log.setFo
log = logging.getLogger("instruments")
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)-15s %(name)-5s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
log.addHandler(handler)
log.setLevel(logging.DEBUG)
log = logging.getLogger('root')
log.addFilter(logging.Filter('root'))
# log.addFilter(ContextFilter())
#start-up #start-up
#log denotes write to local, report denotes sending to server #log denotes write to local, report denotes sending to server
#TODO startup message
#loadconfig #loadconfig
config = configparser.ConfigParser() from config import *
config.sections()
config.read('config.ini')
refresh_camera_local = float(config['refresh rates']['refresh camera local'])
refresh_camera_transmit = float(config['refresh rates']['refresh camera transmit'])
refresh_barometer_local = float(config['refresh rates']['refresh barometer local']) mission = Mission()
refresh_barometer_transmit = float(config['refresh rates']['refresh barometer transmit'])
refresh_gps_local = float(config['refresh rates']['refresh gps local']) #todo test cell connection
refresh_gps_transmit = float(config['refresh rates']['refresh gps transmit']) reporter = Datareporter (
missiontime = mission,
use_lan = True,
url = url,
server_port = 5010,
data_path = data_path,
image_path = image_path,
ping_path = ping_path,
com_port_name=com_port_name,
baud_rate = baud_rate
)
low_quality_resolution = eval(config['camera settings']['low quality resolution']) t = Record({'zt':mission.timezero})
low_quality_compression_pct = int(config['camera settings']['low quality compression pct']) # mid = reporter.send(t) #reply of first message is the mission number
report_url = config['report settings']['report url'] # mission.set_mid(mid)
report_image_url = config['report settings']['report image url']
com_port_name = config['modem settings']['com port name'] notebook = Datalogger (
baud_rate = config['modem settings']['baud rate'] missiontime = mission,
text_path=None,
log_path=log_path,
photo_path=photo_path)
logger = Datalogger () log.debug ("System started", extra={'MISSION_TIME': "", 'MISSION_ID':""})
log.debug ("System started")
# logger.log("Log Intiated")
#system check #system check
system = System() system = System()
system_status = system.status log.debug (system.stats, extra={'MISSION_TIME': "", 'MISSION_ID':""})
# logger.log (system_status[1]) #set mission time
#todo test cell connection, log, report log.info("Mission {} started, time zero is {}".format(mission.name, mission.timezero), extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
reporter = Datareporter()
reporter_status = reporter.status
# logger.log (reporter_status[1])
reporter.send(reporter_status[1])
reporter.send(system_status[1])
#TODO test camera, log, report
#intiate mission
# log.info('Sent {} to server'.format(intiate))
#TODO test camera
camera = Camera (low_quality_compression_pct=low_quality_compression_pct, camera = Camera (low_quality_compression_pct=low_quality_compression_pct,
low_quality_resolution=low_quality_resolution) low_quality_resolution=low_quality_resolution,
camera_status = camera.status high_quality_compression_pct=high_quality_compression_pct,
high_quality_resolution=high_quality_resolution,
vflip=False,
hflip=False,
exposure_mode='sports',
debug=True
)
#todo test barometer, log, report #todo test barometer
barometer = Barometer() barometer = Barometer(debug=True)
# barometer_status = barometer.status
# logger.log (barometer_status[1])
# reporter.send(barometer_status[1])
#todo test GPS, log, report #todo test GPS, log, report
#todo check for errors, throw exception if error #todo check for errors, throw exception if error
if(system_status[0] or reporter_status[0] or camera_status[0]): class scheduler ():
raise Exception ('Error')
def scheduler (interval, worker_func, iterations = 0): def __init__(self, interval, function, *args, **kwargs):
if iterations != 1: self._timer = None
threading.Timer ( self.interval = interval
interval, self.function = function
scheduler, [interval, worker_func, 0 if iterations == 0 else iterations-1] self.args = args
).start () self.kwargs = kwargs
self.is_running = False
self.start()
worker_func () def _run(self):
self.is_running = False
self.start()
self.function(*self.args, **self.kwargs)
#while 1: def start(self):
if not self.is_running:
self._timer = Timer(self.interval, self._run)
self._timer.start()
self.is_running = True
image = camera.capture() def stop(self):
data = barometer.read() self._timer.cancel()
self.is_running = False
def update_barometer_local():
global bar
bar = barometer.read()
bar.update({'mt':mission.now()})
record = Record(bar.copy(),'b')
log.info('Updating barometer...',extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
notebook.log(record)
def update_image_local():
global img
img = camera.capture(name=mission.now())
record = Record([img.get('hi'), img.get('lo')], 'i')
log.info('Updating image...', extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
notebook.log(record)
def submit_report():
global bar
global img
global counter
global transpondence
# reporter.create_transpondence()
if not transpondence:
log.info('Creating transpondence',extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
transpondence = Record()
if (counter % refresh_barometer_transmit) == 0:
log.info('Adding barometer data to transpondence',extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
transpondence.add(bar,'b')
if (counter % refresh_camera_transmit) == 0:
log.info('Adding image to transpondence',extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
transpondence.add(img.get('lo'),'i')
log.info('Sending transpondence', extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
transpondence = reporter.send(transpondence) #returns none on success, (bad practice?)
counter += 1
log.debug('Counter = {}'.format(counter), extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
counter=1
img = None
bar = None
transpondence = None
scheduler(1, update_barometer_local)
scheduler(refresh_camera_local, update_image_local)
scheduler(2, submit_report)
# logger.log(image.get('hi'), type='image')
# logger.log(barometer, type='data')
reporter.send(image.get('lo'), type='data')
reporter.send(barometer, type = 'data')
#todo break apart log and report refresh
# scheduler(refresh_barometer_local, update_barometer, 2)
# scheduler(refresh_camera_local, update_camera, 2)
pass pass

43
mission.py Normal file
View File

@@ -0,0 +1,43 @@
__author__ = 'asc'
import time
import datetime
import random, string
# def generate_mid():
# new_mission_id = ''.join(random.SystemRandom().choice(string.ascii_uppercase) for _ in range(2))
# return new_mission_id
class Mission():
def __init__(self, timezero=None, mid=None):
if not timezero:
timezero = datetime.datetime.now()
self._zero=timezero
self._mid = mid
# self._mid=generate_mid()
def set_mid(self, mid):
self._mid = mid
def now(self):
#returns a string
d=datetime.datetime.now()-self._zero
return d.total_seconds()
@property
def timezero(self):
return self._zero
def to_absolutetime(self, mission_time):
return self._zero + datetime.timedelta(seconds=float(mission_time))
@property
def mid(self):
if self._mid:
mid = self._mid
else:
mid= None
return self._mid
@property
def name(self):
return self._mid

28
post_encode.py Normal file
View File

@@ -0,0 +1,28 @@
__author__ = 'asc'
def encode_multipart_formdata(fields, files):
"""
fields is a sequence of (name, value) elements for regular form fields.
files is a sequence of (name, filename, value) elements for
data to be uploaded as files
Return (content_type, body) ready for http.client connection instance
"""
BOUNDARY_STR = '----------ThIs_Is_tHe_bouNdaRY_$'
CRLF = bytes("\r\n","ASCII")
L = []
for (key, value) in fields:
L.append(bytes("--" + BOUNDARY_STR,"ASCII"))
L.append(bytes('Content-Disposition: form-data; name="%s"' % key,"ASCII"))
L.append(b'')
L.append(bytes(value,"ASCII"))
for (key, filename, value) in files:
L.append(bytes('--' + BOUNDARY_STR,"ASCII"))
L.append(bytes('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename),"ASCII"))
L.append(bytes('Content-Type: %s' % get_content_type(filename),"ASCII"))
L.append(b'')
L.append(value)
L.append(bytes('--' + BOUNDARY_STR + '--',"ASCII"))
L.append(b'')
body = CRLF.join(L)
content_type = 'multipart/form-data; boundary=' + BOUNDARY_STR
return content_type, body

View File

@@ -3,7 +3,7 @@ import os
import psutil import psutil
class System_Debug(): class System():
def __init__(self): def __init__(self):
#TODO add system initialization #TODO add system initialization
@@ -17,7 +17,7 @@ class System_Debug():
import os import os
# Return CPU temperature as a character string # Return CPU temperature as a character string
def getCPUtemperature(): def getCPUtemperature(self):
res = os.popen('vcgencmd measure_temp').readline() res = os.popen('vcgencmd measure_temp').readline()
return(res.replace("temp=","").replace("'C\n","")) return(res.replace("temp=","").replace("'C\n",""))
@@ -25,22 +25,20 @@ class System_Debug():
p=psutil.cpu_percent(interval=1) p=psutil.cpu_percent(interval=1)
return p return p
def getRAMinfo(): def getRAMinfo(self):
p = psutil.virtual_memory() p = dict(psutil.virtual_memory()._asdict())
p = psutil.swap_memory() p = dict(psutil.swap_memory()._asdict())
return p return p
def getDiskSpace(): def getDiskSpace(self):
p = psutil.disk_usage('/') p = dict(psutil.disk_usage('/')._asdict())
return p return p
@property @property
def stats (self): def stats (self):
#courtesy of Phillipe
# https://www.raspberrypi.org/forums/memberlist.php?mode=viewprofile&u=40834&sid=dd38cc12161ac10b324ed2a2238972d3
# CPU informatiom # CPU informatiom
CPU_temp = self.getCPUtemperature() CPU_temp = self.getCPUtemperature()
CPU_usage = self.getCPUuse() CPU_usage = self.getCPUusage()
# RAM information # RAM information
# Output is in kb, here I convert it in Mb for readability # Output is in kb, here I convert it in Mb for readability
@@ -50,7 +48,13 @@ class System_Debug():
# Disk information # Disk information
DISK_stats = self.getDiskSpace() DISK_stats = self.getDiskSpace()
pid = os.getpid()
# print(type(DISK_stats._asdict()))
return {"cpu_temp":CPU_temp, return {"cpu_temp":CPU_temp,
"cpu_usage":CPU_usage, "cpu_usage":CPU_usage,
"ram_stats":RAM_stats, "ram_stats":RAM_stats,
"disk_stats":DISK_stats} "disk_stats":DISK_stats,
"PID":pid}

160
test.py
View File

@@ -1,80 +1,110 @@
import csv, requests __author__ = 'asc'
from instruments import Camera_Debug2 as Camera DEBUG = True
from datahandling import Datareporter_Debug3 as Datareporter import logging
import serial import logging.handlers,logging.config
import sys
import json
REPORTIMAGETOURL = "http://10.0.1.4:5010/photo" from instruments import Barometer, Camera
from datahandling import Datalogger, Datareporter
from system import System as System
import threading
from datahandling import Record
from mission import Mission
import random
import string
LOG_FILE = "log2.txt" logging.config.fileConfig('logging.ini')
import binascii log = logging.getLogger(__name__)
# log = logging.basicConfig()
# log1 = logging.getLogger("instruments")
# log2 = logging.getLogger("datahandling")
# handler = logging.handlers.RotatingFileHandler('spaceballoon.log', backupCount=5)
import base64 formatter = logging.Formatter('[%(asctime)-25s] %(name)-15s %(levelname)-8s %(message)s')
from base64 import urlsafe_b64encode # handler.setFormatter(formatter)
# log.addHandler(handler)
# log1.addHandler(handler)
# log2.addHandler(handler)
log.setLevel(logging.DEBUG)
# log1.setLevel(logging.DEBUG)
# log2.setLevel(logging.INFO)
with open('log2.txt','rb') as f: #start-up
g=f.read() #log denotes write to local, report denotes sending to server
e=urlsafe_b64encode(g)
# a=binascii.b2a_uu(f) #loadconfig
from config import *
# e = f.encode('base64') mission = Mission()
# e = str(e, "ascii") #todo test cell connection
reporter = Datareporter (
missiontime = mission,
use_lan = True,
url = "http://10.0.1.4",
server_port = 5010,
data_path = "upload-data",
image_path = "missions",
ping_path = "ping",
com_port_name="/dev/ttyAMA0",
baud_rate = 9600
)
data = {"temp":1, #intiate mission
"press":3,
"altitude":2,
"image":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
# data = json.dumps(data,ensure_ascii=True)
# camera = Camera(low_quality_resolution=(320, 180), #TODO test camera
# low_quality_compression_pct=50) camera = Camera (low_quality_compression_pct=low_quality_compression_pct,
low_quality_resolution=low_quality_resolution,
high_quality_compression_pct=high_quality_compression_pct,
high_quality_resolution=high_quality_resolution,
vflip=False,
hflip=False,
exposure_mode='sports',
# debug=True
)
# image = camera.capture() #todo test barometer
barometer = Barometer()
# print (image) #todo test GPS, log, report
pass #todo check for errors, throw exception if error
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
def scheduler (interval, worker_func, iterations = 0):
if iterations != 1:
# i=open('resized_image.jpg','rb') threading.Timer (
# r=i.read() interval,
scheduler, [interval, worker_func, 0 if iterations == 0 else iterations-1]
# def callback(encoder, bytes_read): ).start ()
# # Do something with this information
# pass worker_func ()
#
# monitor = MultipartEncoderMonitor.from_fields(
# fields={'field0': 'value0'}, callback def update_barometer_local():
# ) global bar
bar = barometer.read()
bar.update({'mt':mission.now()})
record = Record(bar.copy(),'b')
# m = MultipartEncoder(fields={"field":("image", open("resized_image.jpg", 'rb'), "image/jpeg")}) # notebook.log(record)
#
reporter = Datareporter (content_type="form-data") def update_image_local():
# global img
# f = open('resized_image.jpg','rb') img = camera.capture(name=mission.now(), thumbnail=None)
# r=f.read() record = Record([img.get('hi'), img.get('lo')], 'i')
# notebook.log(record)
#report image
# response = requests.post("http://home.ascorrea.com:5010/upload-file", data=m.read()) transpondence = Record()
# print(type(r))
# reporter.send(str(r)[2:-5].replace("\\\", "\\"), type="image") update_image_local()
# reporter.send("Content-Disposition: {0}; name=\"{1}\"; filename=\"{2}\" {4}".format(
# 'application/octet-stream', transpondence.add(img.get('lo'),'i')
# 'field', transpondence = reporter.send(transpondence) #returns none on success, (bad practice?)
# 'resized_image',
# 'image/jpeg',""), type="image") counter=1
img = None
bar = None
reporter.send(data, type="image") transpondence = None
pass pass

89
test_http.py Normal file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/python3
from test_shared import *
from lib.sim900.inetgsm import SimInetGSM
COMPORT_NAME = "/dev/ttyAMA0"
BAUD_RATE = 9600
#logging levels
CONSOLE_LOGGER_LEVEL = logging.INFO
LOGGER_LEVEL = logging.INFO
def main():
"""
Tests HTTP GET and POST requests.
:return: true if everything was OK, otherwise returns false
"""
#adding & initializing port object
port = initializeUartPort(portName=COMPORT_NAME, baudrate=BAUD_RATE)
#initializing logger
(formatter, logger, consoleLogger,) = initializeLogs(LOGGER_LEVEL, CONSOLE_LOGGER_LEVEL)
#making base operations
d = baseOperations(port, logger)
if d is None:
return False
(gsm, imei) = d
inet = SimInetGSM(port, logger)
logger.info("attaching GPRS")
if not inet.attachGPRS("internet", "", "", 1):
logger.error("error attaching GPRS")
return False
logger.info("ip = {0}".format(inet.ip))
#making HTTP GET request
logger.info("making HTTP GET request")
if not inet.httpGet(
"http://httpbin.org",
80,
"/ip"
):
logger.error("error making HTTP GET request: {0}".format(inet.errorText))
return False
logger.info("httpResult = {0}".format(inet.httpResult))
if inet.httpResponse is not None:
response = str(inet.httpResponse).replace("\n\r", "\n")
logger.info("response: \"{0}\"".format(response))
else:
logger.info("empty response")
#making 3 http post requests
for i in range(3):
logger.info("making HTTP POST request #{0}".format(i))
if not inet.httpPOST(
"home.ascorrea.com",
5010,
"/report-encoded?action=change&ip=test2",
"action=change&ip=test"
):
print("[FAILED]")
return False
logger.info("httpResult = {0}".format(inet.httpResult))
if inet.httpResponse is not None:
response = str(inet.httpResponse).replace("\n\r", "\n")
logger.info("response: \"{0}\"".format(response))
else:
logger.info("empty response")
logger.debug("detaching GPRS")
if not inet.dettachGPRS():
logger.error("error detaching GRPS: {0}".format(inet.errorText))
return False
gsm.closePort()
return True
if __name__ == "__main__":
main()
print("DONE")

88
test_http_2.py Normal file
View File

@@ -0,0 +1,88 @@
#!/usr/bin/python3
from test_shared import *
from lib.sim900.inetgsm import SimInetGSM
COMPORT_NAME = "/dev/ttyAMA0"
BAUD_RATE = 9600
#logging levels
CONSOLE_LOGGER_LEVEL = logging.INFO
LOGGER_LEVEL = logging.INFO
def main():
"""
Tests HTTP GET and POST requests.
:return: true if everything was OK, otherwise returns false
"""
#adding & initializing port object
port = initializeUartPort(portName=COMPORT_NAME, baudrate=BAUD_RATE)
#initializing logger
(formatter, logger, consoleLogger,) = initializeLogs(LOGGER_LEVEL, CONSOLE_LOGGER_LEVEL)
#making base operations
d = baseOperations(port, logger)
if d is None:
return False
(gsm, imei) = d
inet = SimInetGSM(port, logger)
logger.info("attaching GPRS")
if not inet.attachGPRS("internet", "", "", 1):
logger.error("error attaching GPRS")
return False
logger.info("ip = {0}".format(inet.ip))
#making HTTP GET request
logger.info("making HTTP GET request")
if not inet.httpGet(
"http://httpbin.org",
80,
"/ip"
):
logger.error("error making HTTP GET request: {0}".format(inet.errorText))
return False
logger.info("httpResult = {0}".format(inet.httpResult))
if inet.httpResponse is not None:
response = str(inet.httpResponse).replace("\n\r", "\n")
logger.info("response: \"{0}\"".format(response))
else:
logger.info("empty response")
if not inet.httpPOST(
"home.ascorrea.com",
5010,
"/upload-file",
content="image/jpeg",
parameters=str({"data":open('temp_img_lo.jpg','rb').read()))
):
print("[FAILED]")
return False
logger.info("httpResult = {0}".format(inet.httpResult))
if inet.httpResponse is not None:
response = str(inet.httpResponse).replace("\n\r", "\n")
logger.info("response: \"{0}\"".format(response))
else:
logger.info("empty response")
logger.debug("detaching GPRS")
if not inet.dettachGPRS():
logger.error("error detaching GRPS: {0}".format(inet.errorText))
return False
gsm.closePort()
return True
if __name__ == "__main__":
main()
print("DONE")