Compare commits
6 Commits
master
...
7dbe9281df
| Author | SHA1 | Date | |
|---|---|---|---|
|
7dbe9281df
|
|||
|
5234446be2
|
|||
|
364f1c51fc
|
|||
|
75715c4749
|
|||
|
800e6f1b7b
|
|||
|
7954468602
|
83
cell_connection.py
Normal file
83
cell_connection.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
__author__ = 'asc'
|
||||||
|
|
||||||
|
from lib.sim900.gsm import SimGsm, SimGsmPinRequestState
|
||||||
|
from lib.sim900.imei import SimImeiRetriever
|
||||||
|
import serial
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def initializeUartPort(
|
||||||
|
portName = "/dev/ttyAMA0",
|
||||||
|
baudrate = 9600,
|
||||||
|
bytesize = serial.EIGHTBITS,
|
||||||
|
parity = serial.PARITY_NONE,
|
||||||
|
stopbits = serial.STOPBITS_ONE,
|
||||||
|
timeout = 0
|
||||||
|
):
|
||||||
|
|
||||||
|
port = serial.Serial()
|
||||||
|
|
||||||
|
#tuning port object
|
||||||
|
port.port = portName
|
||||||
|
port.baudrate = baudrate
|
||||||
|
port.bytesize = bytesize
|
||||||
|
port.parity = parity
|
||||||
|
port.stopbits = stopbits
|
||||||
|
port.timeout = timeout
|
||||||
|
|
||||||
|
return port
|
||||||
|
|
||||||
|
def initializeLogs(loggerLevel, consoleLoggerLevel):
|
||||||
|
#initializing logging formatter
|
||||||
|
formatter = logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s')
|
||||||
|
|
||||||
|
#initializing logger
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(loggerLevel)
|
||||||
|
|
||||||
|
#initializing console handler for logging
|
||||||
|
consoleLogger = logging.StreamHandler(sys.stdout)
|
||||||
|
consoleLogger.setLevel(consoleLoggerLevel)
|
||||||
|
consoleLogger.setFormatter(formatter)
|
||||||
|
|
||||||
|
#adding console appender
|
||||||
|
logger.addHandler(consoleLogger)
|
||||||
|
|
||||||
|
return (formatter, logger, consoleLogger,)
|
||||||
|
|
||||||
|
def baseOperations(port, logger):
|
||||||
|
#class for general functions
|
||||||
|
gsm = SimGsm(port, logger)
|
||||||
|
|
||||||
|
#opening COM port
|
||||||
|
logger.info("opening port")
|
||||||
|
if not gsm.openPort():
|
||||||
|
logger.error("error opening port: {0}".format(gsm.errorText))
|
||||||
|
return None
|
||||||
|
|
||||||
|
#initializing session with SIM900
|
||||||
|
logger.info("initializing SIM900 session")
|
||||||
|
if not gsm.begin(5):
|
||||||
|
logger.error("error initializing session: {0}".format(gsm.errorText))
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.debug("checking PIN state")
|
||||||
|
if gsm.pinState != SimGsmPinRequestState.NOPINNEEDED:
|
||||||
|
logger.debug("PIN needed, entering")
|
||||||
|
if gsm.pinState == SimGsmPinRequestState.SIM_PIN:
|
||||||
|
if not gsm.enterPin("1111"):
|
||||||
|
logger.error("error entering PIN")
|
||||||
|
else:
|
||||||
|
logger.debug("PIN OK")
|
||||||
|
|
||||||
|
#retrieving IMEI
|
||||||
|
sim = SimImeiRetriever(port, logger)
|
||||||
|
logger.info("retrieving IMEI")
|
||||||
|
imei = sim.getIMEI()
|
||||||
|
if imei is None:
|
||||||
|
logger.error("error retrieving IMEI: {0}".format(sim.errorText))
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.info("IMEI = {0}".format(imei))
|
||||||
|
|
||||||
|
return (gsm, imei)
|
||||||
33
config.ini
Normal file
33
config.ini
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[refresh rates]
|
||||||
|
refresh camera local = 10
|
||||||
|
refresh camera transmit = 15
|
||||||
|
|
||||||
|
refresh barometer local = 2
|
||||||
|
refresh barometer transmit = 2
|
||||||
|
|
||||||
|
refresh gps local = 2
|
||||||
|
refresh gps transmit = 5
|
||||||
|
|
||||||
|
refresh system = 10
|
||||||
|
|
||||||
|
[camera settings]
|
||||||
|
low quality resolution = (320, 240)
|
||||||
|
low quality compression pct = 7
|
||||||
|
high quality resolution = (2592,1944)
|
||||||
|
high quality compression pct = 100
|
||||||
|
|
||||||
|
[modem settings]
|
||||||
|
com port name = "/dev/ttyAMA0"
|
||||||
|
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
35
config.py
Normal 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
154
data_handling_legacy.py
Normal 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
|
||||||
312
datahandling.py
312
datahandling.py
@@ -1,85 +1,281 @@
|
|||||||
__author__ = 'asc'
|
__author__ = 'asc'
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
from urllib import request
|
from urllib import request, parse
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
from cell_connection import initializeUartPort, baseOperations, initializeLogs
|
||||||
|
import logging
|
||||||
|
from requests_toolbelt import MultipartEncoder
|
||||||
|
import csv
|
||||||
|
import base64
|
||||||
|
|
||||||
SCRIPTDIR = os.path.dirname(os.path.abspath(__file__))
|
SCRIPTDIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
REPORTTOURL = "http://10.0.1.4:5010/report"
|
|
||||||
REPORTIMAGETOURL = "http://10.0.1.4:5010/photo"
|
|
||||||
|
|
||||||
class Datalogger_Debug ():
|
logger = logging.getLogger(__name__)
|
||||||
def __init__(self, path=SCRIPTDIR):
|
|
||||||
|
def add_keys_if_necessary(existing_file, test_dict):
|
||||||
|
all=None
|
||||||
|
try:
|
||||||
|
with open(existing_file,'r') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
header = []
|
||||||
|
try:
|
||||||
|
header = next(reader)
|
||||||
|
except StopIteration as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if len(header) == 0:
|
||||||
|
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
|
pass
|
||||||
|
|
||||||
def log(self, message, type="text"):
|
def log(self, r):
|
||||||
if type == "text":
|
self._record = r._get_dict()
|
||||||
print ("%s - Log Message: %s"% (str(datetime.datetime.now()), message))
|
# logger.info('Recording {}'.format(self._record))
|
||||||
elif type == "image":
|
for k in self._record.keys():
|
||||||
print ("%s - Log Image: %s"% (str(datetime.datetime.now()), message))
|
if k is 'b':
|
||||||
else:
|
if self._record[k] is not list:
|
||||||
raise Exception
|
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'))
|
||||||
|
|
||||||
class Datareporter_Debug ():
|
folder = os.path.join(self.photo_path, self._mt.mid)
|
||||||
def __init__(self, path=SCRIPTDIR):
|
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
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
missiontime,
|
||||||
|
url,
|
||||||
|
data_path,
|
||||||
|
image_path,
|
||||||
|
ping_path,
|
||||||
|
server_port,
|
||||||
|
com_port_name=None,
|
||||||
|
baud_rate=None,
|
||||||
|
use_lan = False,
|
||||||
|
path=SCRIPTDIR):
|
||||||
#TODO communication
|
#TODO communication
|
||||||
pass
|
self.mt = missiontime
|
||||||
|
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.baud_rate = baud_rate
|
||||||
|
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)
|
||||||
|
|
||||||
@property
|
if not d is None:
|
||||||
def status (self):
|
(self.gsm, self.imei) = d
|
||||||
return (0, "Data reporter functioning properly")
|
|
||||||
|
|
||||||
def send(self, message, type="text"):
|
self.inet = self.SimInetGSM(self.port, logger)
|
||||||
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 ():
|
logger.info('ip = {0}'.format(self.inet.ip), extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
|
||||||
def __init__(self, path=SCRIPTDIR):
|
|
||||||
#TODO communication
|
logger.debug('attaching GPRS', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
|
||||||
pass
|
|
||||||
|
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(REPORTTOURL).read().decode()).get('status')
|
|
||||||
|
return (0, 'Data reporter functioning properly')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return (1, 'Data reporter error: %s' % e)
|
||||||
|
|
||||||
|
def _send_data(self, message, _intiating_report=False):
|
||||||
|
response=None
|
||||||
|
m = MultipartEncoder(fields=message)
|
||||||
|
|
||||||
|
if _intiating_report:
|
||||||
|
path = '{0}'.format(self.image_path)
|
||||||
|
else:
|
||||||
|
path = '{0}/{1}'.format(self.image_path,self.mt.mid)
|
||||||
|
|
||||||
|
if self.use_lan:
|
||||||
|
logger.info ('Sending transpondence using LAN', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
|
||||||
|
response = requests.post('{0}:{1}/{2}'.format(self.url, self.server_port, path), data=m.read(), headers={'Content-Type': m.content_type})
|
||||||
pass
|
pass
|
||||||
if not check:
|
|
||||||
return (0, "Data reporter functioning properly")
|
elif not self.use_lan:
|
||||||
|
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
|
||||||
|
|
||||||
|
logger.debug('ip = {0}'.format(self.inet.ip), extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
|
||||||
|
|
||||||
|
logger.debug('making HTTP POST request', extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
|
||||||
|
|
||||||
|
if not self.inet.httpPOST(
|
||||||
|
self.url,
|
||||||
|
self.server_port,
|
||||||
|
"/{}".format(path),
|
||||||
|
m.to_string(),
|
||||||
|
contentType=m.content_type
|
||||||
|
):
|
||||||
|
logger.error('error making HTTP POST: {0}'.format(self.inet.errorText), extra={'MISSION_TIME': self.mt.now(), 'MISSION_ID':self.mt.name})
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.inet.httpResponse is not None:
|
||||||
|
response = str(self.inet.httpResponse).replace('\n\r', '\n')
|
||||||
else:
|
else:
|
||||||
return (1, check)
|
response = 'empty response'
|
||||||
except Exception as e:
|
|
||||||
return (1, "Data reporter error: %s" % e)
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def send(self, message, type="text"):
|
|
||||||
try:
|
|
||||||
if type == "text":
|
|
||||||
#TODO send text
|
|
||||||
print ("%s - Sent Message: %s"% (str(datetime.datetime.now()), message))
|
|
||||||
elif type == "image":
|
|
||||||
#todo send image
|
|
||||||
response = requests.post(REPORTIMAGETOURL, 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(REPORTTOURL)
|
|
||||||
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
|
|
||||||
return (0, "Reporter error: %s" % e)
|
|
||||||
|
|
||||||
|
|||||||
261
instruments.py
261
instruments.py
@@ -2,99 +2,202 @@ __author__ = 'asc'
|
|||||||
debug = True
|
debug = True
|
||||||
from random import random
|
from random import random
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import base64
|
||||||
|
|
||||||
class Barometer_Debug ():
|
logger = logging.getLogger(__name__)
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
class Camera:
|
||||||
def status (self):
|
def __init__(self,
|
||||||
return (0, "Barometer functioning properly")
|
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))
|
||||||
|
|
||||||
@property
|
logger.debug("Camera intialized", extra={'MISSION_TIME': "", 'MISSION_ID':""})
|
||||||
def temperature (self):
|
pass
|
||||||
if self.status[0]:
|
|
||||||
return 'error'
|
|
||||||
|
|
||||||
if debug:
|
@property
|
||||||
temp = random()*100
|
def status (self):
|
||||||
else:
|
return (0, "Camera functioning properly")
|
||||||
raise Exception ('Not Debug')
|
|
||||||
return temp
|
|
||||||
|
|
||||||
@property
|
def capture (self, name, no_low_quality=False, no_high_quality=False, **kwargs):
|
||||||
def pressure (self):
|
#todo image adjustments
|
||||||
if (self.status[0]):
|
img_hi = None
|
||||||
return 'error'
|
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 debug:
|
if not no_low_quality:
|
||||||
press = random()
|
time.sleep(1)
|
||||||
else:
|
logger.debug('Taking low quality photo (Resolution: {}, JPEG Quality: {}%)'.format(self.low_quality_resolution, self.low_quality_compression_pct))
|
||||||
raise Exception ('Not Debug')
|
self._cam.capture("temp_img_lo.jpg",
|
||||||
return press
|
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':""})
|
||||||
|
|
||||||
@property
|
if not no_low_quality:
|
||||||
def altitude (self):
|
with open("temp_img_lo.jpg", 'rb') as f:
|
||||||
if self.status[0]:
|
img_lo = str(base64.b64encode(f.read()),'ascii')
|
||||||
return 'error'
|
# img_lo = str(f.read())
|
||||||
if debug:
|
r['lo']=("{}_lo.jpg".format(name), img_lo, "image/jpeg")
|
||||||
alt = random()*100000
|
logger.info('Low quality photo taken', extra={'MISSION_TIME': "", 'MISSION_ID':""})
|
||||||
else:
|
|
||||||
raise Exception ('Not Debug')
|
|
||||||
|
|
||||||
return alt
|
return r
|
||||||
|
|
||||||
class Camera_Debug():
|
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
|
||||||
|
|
||||||
def __init__(self):
|
@property
|
||||||
pass
|
def status (self):
|
||||||
|
return (0, "Barometer functioning properly")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status (self):
|
def temperature (self):
|
||||||
return (0, "Camera functioning properly")
|
if self.status[0]:
|
||||||
|
return 'error'
|
||||||
|
|
||||||
def capture (self):
|
temp = self.bmp.read_temperature()
|
||||||
#todo capture image
|
return temp
|
||||||
return ({"file":open("image.jpg", 'rb')})
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pressure (self):
|
||||||
|
if (self.status[0]):
|
||||||
|
return 'error'
|
||||||
|
|
||||||
|
# press = 100000*random()
|
||||||
|
press = self.bmp.read_pressure()
|
||||||
|
|
||||||
|
return press
|
||||||
|
|
||||||
|
@property
|
||||||
|
def altitude (self):
|
||||||
|
if self.status[0]:
|
||||||
|
return 'error'
|
||||||
|
#TODO Set the altitude of your current location in meter
|
||||||
|
alt = self.bmp.read_altitude()
|
||||||
|
return alt
|
||||||
|
|
||||||
|
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):
|
||||||
|
logger.debug("Intializing GPS")
|
||||||
|
# self.bmp = BMP085.BMP085()
|
||||||
|
logger.debug("Gps intilized")
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status (self):
|
||||||
|
return (0, "Barometer functioning properly")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature (self):
|
||||||
|
if self.status[0]:
|
||||||
|
return 'error'
|
||||||
|
|
||||||
|
temp = self.bmp.read_temperature()
|
||||||
|
return temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pressure (self):
|
||||||
|
if (self.status[0]):
|
||||||
|
return 'error'
|
||||||
|
|
||||||
|
# press = 100000*random()
|
||||||
|
press = self.bmp.read_pressure()
|
||||||
|
|
||||||
|
return press
|
||||||
|
|
||||||
|
@property
|
||||||
|
def altitude (self):
|
||||||
|
if self.status[0]:
|
||||||
|
return 'error'
|
||||||
|
#TODO Set the altitude of your current location in meter
|
||||||
|
alt = self.bmp.read_altitude()
|
||||||
|
return alt
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
logger.debug("Reading from barometer")
|
||||||
|
#refresh each instrument
|
||||||
|
alt = self.altitude
|
||||||
|
press = self.pressure
|
||||||
|
temp = self.temperature
|
||||||
|
result = {"a":alt,
|
||||||
|
"t":temp,
|
||||||
|
"p":press,
|
||||||
|
}
|
||||||
|
logger.debug("Gps reads {}".format(result))
|
||||||
|
|
||||||
|
return result
|
||||||
|
# class Gps:
|
||||||
|
|
||||||
#barometerdebug2 uses actual barometer
|
|
||||||
# class Barometer_Debug2 ():
|
|
||||||
# import Adafruit_BMP.BMP085 as BMP085
|
|
||||||
# def __init__(self):
|
|
||||||
# self.bmp = BMP085.BMP085()
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def status (self):
|
|
||||||
# return (0, "Barometer functioning properly")
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def temperature (self):
|
|
||||||
# if self.status[0]:
|
|
||||||
# return 'error'
|
|
||||||
#
|
|
||||||
# temp = self.bmp.read_temperature()
|
|
||||||
# return temp
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def pressure (self):
|
|
||||||
# if (self.status[0]):
|
|
||||||
# return 'error'
|
|
||||||
#
|
|
||||||
# # press = 100000*random()
|
|
||||||
# press = self.bmp.read_pressure()
|
|
||||||
#
|
|
||||||
# return press
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def altitude (self):
|
|
||||||
# if self.status[0]:
|
|
||||||
# return 'error'
|
|
||||||
# #TODO Set the altitude of your current location in meter
|
|
||||||
# alt = self.bmp.read_altitude()
|
|
||||||
# # alt = 100000*random()
|
|
||||||
# return alt
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
0
lib/__init__.py
Normal file
0
lib/__init__.py
Normal file
0
lib/sim900.bak/__init__.py
Normal file
0
lib/sim900.bak/__init__.py
Normal file
0
lib/sim900.bak/__stand_alone__.py
Normal file
0
lib/sim900.bak/__stand_alone__.py
Normal file
60
lib/sim900.bak/amsharedmini.py
Normal file
60
lib/sim900.bak/amsharedmini.py
Normal 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
661
lib/sim900.bak/gsm.py
Normal 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
45
lib/sim900.bak/imei.py
Normal 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
622
lib/sim900.bak/inetgsm.py
Normal 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;
|
||||||
62
lib/sim900.bak/simshared.py
Normal file
62
lib/sim900.bak/simshared.py
Normal 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
|
||||||
653
lib/sim900.bak/smshandler.py
Normal file
653
lib/sim900.bak/smshandler.py
Normal 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
|
||||||
126
lib/sim900.bak/ussdhandler.py
Normal file
126
lib/sim900.bak/ussdhandler.py
Normal 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
|
||||||
|
|
||||||
0
lib/sim900/__init__.py
Normal file
0
lib/sim900/__init__.py
Normal file
0
lib/sim900/__stand_alone__.py
Normal file
0
lib/sim900/__stand_alone__.py
Normal file
60
lib/sim900/amsharedmini.py
Normal file
60
lib/sim900/amsharedmini.py
Normal 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
|
||||||
675
lib/sim900/gsm.py
Normal file
675
lib/sim900/gsm.py
Normal file
@@ -0,0 +1,675 @@
|
|||||||
|
#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
|
||||||
|
"""
|
||||||
|
if not isinstance(commandString, bytes):
|
||||||
|
data = bytearray(commandString, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
|
||||||
|
else:
|
||||||
|
data = commandString + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
|
||||||
|
return self.__sendRawBytes(data)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
: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.printLn(commandLine, encoding)
|
||||||
|
|
||||||
|
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 = 5000, 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/imei.py
Normal file
45
lib/sim900/imei.py
Normal 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()
|
||||||
676
lib/sim900/inetgsm.py
Normal file
676
lib/sim900/inetgsm.py
Normal file
@@ -0,0 +1,676 @@
|
|||||||
|
#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
|
||||||
|
|
||||||
|
def chunks(l, n):
|
||||||
|
n = max(1, n)
|
||||||
|
return [l[i:i + n] for i in range(0, len(l), n)]
|
||||||
|
|
||||||
|
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 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):
|
||||||
|
"""
|
||||||
|
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, contentType="application/x-www-form-urlencoded", 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\",\"{0}\"".format(contentType), 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},60000".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)
|
||||||
|
|
||||||
|
# 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"):
|
||||||
|
self.setError("{0}: can't upload HTTP POST data {1}".format(inspect.stack()[0][3], dataLine))
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
: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+CGATT=1", 1000 ],
|
||||||
|
# ["AT+CSNS=4,", 500 ],
|
||||||
|
# ["AT+CSTT=\"{}\",\"{}\",\"{}\"".format("wholesale", "", ""), 500 ],
|
||||||
|
# ["AT+CIICR", 500 ],
|
||||||
|
# ["AT+CIFSR", 10000 ]
|
||||||
|
[ "AT+CIPSTART=\"TCP\",\"{0}{2}\",\"{1}\"".format(server, port, path), 500 ],
|
||||||
|
[ "AT+CIPSEND=", 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;
|
||||||
62
lib/sim900/simshared.py
Normal file
62
lib/sim900/simshared.py
Normal 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
|
||||||
653
lib/sim900/smshandler.py
Normal file
653
lib/sim900/smshandler.py
Normal 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
|
||||||
126
lib/sim900/ussdhandler.py
Normal file
126
lib/sim900/ussdhandler.py
Normal 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
|
||||||
|
|
||||||
52
logging.ini
Normal file
52
logging.ini
Normal 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
|
||||||
233
main.py
233
main.py
@@ -1,127 +1,174 @@
|
|||||||
__author__ = 'asc'
|
__author__ = 'asc'
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
import logging
|
||||||
|
import logging.handlers,logging.config
|
||||||
|
|
||||||
if DEBUG:
|
from instruments import Barometer, Camera
|
||||||
from instruments import Barometer_Debug as Barometer
|
from datahandling import Datalogger, Datareporter
|
||||||
from instruments import Camera_Debug 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
|
||||||
|
|
||||||
# else:
|
# class ContextFilter(logging.Filter):
|
||||||
# from instruments import Barometer, Camera
|
# """
|
||||||
# from datahandling import Datalogger, Datareporter
|
# This is a filter which injects contextual information into the log.
|
||||||
# from system import System
|
# """
|
||||||
# import threading
|
# def filter(self, record):
|
||||||
|
# record.MYVAR = MYVAR
|
||||||
|
# return True
|
||||||
|
|
||||||
|
logging.config.fileConfig('logging.ini')
|
||||||
|
|
||||||
|
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
|
||||||
|
from config import *
|
||||||
|
|
||||||
logger = Datalogger ()
|
mission = Mission()
|
||||||
logger.log ("System Startup")
|
|
||||||
logger.log("Log Intiated")
|
#todo test cell connection
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
t = Record({'zt':mission.timezero})
|
||||||
|
# mid = reporter.send(t) #reply of first message is the mission number
|
||||||
|
|
||||||
|
# mission.set_mid(mid)
|
||||||
|
|
||||||
|
notebook = Datalogger (
|
||||||
|
missiontime = mission,
|
||||||
|
text_path=None,
|
||||||
|
log_path=log_path,
|
||||||
|
photo_path=photo_path)
|
||||||
|
|
||||||
|
log.debug ("System started", extra={'MISSION_TIME': "", 'MISSION_ID':""})
|
||||||
|
|
||||||
#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
|
||||||
camera = Camera ()
|
|
||||||
camera_status = camera.status
|
|
||||||
logger.log (camera_status[1])
|
|
||||||
reporter.send(camera_status[1])
|
|
||||||
|
|
||||||
#todo test barometer, log, report
|
|
||||||
barometer = Barometer()
|
|
||||||
barometer_status = barometer.status
|
# log.info('Sent {} to server'.format(intiate))
|
||||||
logger.log (barometer_status[1])
|
|
||||||
reporter.send(barometer_status[1])
|
#TODO test camera
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
#todo test barometer
|
||||||
|
barometer = Barometer(debug=True)
|
||||||
|
|
||||||
#todo test GPS, log, report
|
#todo test GPS, log, report
|
||||||
|
|
||||||
#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]):
|
|
||||||
raise Exception ('Error')
|
|
||||||
|
|
||||||
if DEBUG:
|
class scheduler ():
|
||||||
#rate refresh hi-res camera images, saved locally, in seconds
|
|
||||||
refresh_camera_local = 2
|
|
||||||
refresh_camera_transmit = 5
|
|
||||||
|
|
||||||
refresh_barometer_local = 1
|
def __init__(self, interval, function, *args, **kwargs):
|
||||||
refresh_barometer_transmit = 5
|
self._timer = None
|
||||||
|
self.interval = interval
|
||||||
|
self.function = function
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.is_running = False
|
||||||
|
self.start()
|
||||||
|
|
||||||
refresh_gps_local = 2
|
def _run(self):
|
||||||
refresh_gps_transmit = 5
|
self.is_running = False
|
||||||
|
self.start()
|
||||||
|
self.function(*self.args, **self.kwargs)
|
||||||
|
|
||||||
else:
|
def start(self):
|
||||||
refresh_camera_local = 10
|
if not self.is_running:
|
||||||
refresh_camera_transmit= 60
|
self._timer = Timer(self.interval, self._run)
|
||||||
|
self._timer.start()
|
||||||
|
self.is_running = True
|
||||||
|
|
||||||
refresh_barometer_local = 10
|
def stop(self):
|
||||||
refresh_barometer_transmit= 60
|
self._timer.cancel()
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
refresh_gps_local = 10
|
def update_barometer_local():
|
||||||
refresh_gps_transmit= 60
|
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_barometer():
|
def update_image_local():
|
||||||
#refresh each instrument
|
global img
|
||||||
alt = barometer.altitude
|
img = camera.capture(name=mission.now())
|
||||||
press = barometer.pressure
|
record = Record([img.get('hi'), img.get('lo')], 'i')
|
||||||
temp = barometer.temperature
|
log.info('Updating image...', extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
|
||||||
|
notebook.log(record)
|
||||||
|
|
||||||
#log instrument info
|
def submit_report():
|
||||||
logger.log({"altitude":alt,
|
global bar
|
||||||
"temperature":temp,
|
global img
|
||||||
"pressure":press,
|
global counter
|
||||||
"sent":datetime.datetime.now()
|
global transpondence
|
||||||
})
|
|
||||||
|
|
||||||
#report instrument info
|
# reporter.create_transpondence()
|
||||||
reporter.send({"altitude":alt,
|
|
||||||
"temperature": temp,
|
if not transpondence:
|
||||||
"pressure":press,
|
log.info('Creating transpondence',extra={'MISSION_TIME': mission.now(), 'MISSION_ID':mission.name})
|
||||||
"sent":datetime.datetime.now()
|
transpondence = Record()
|
||||||
},type="data")
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def update_camera():
|
|
||||||
image = camera.capture()
|
|
||||||
#log image
|
|
||||||
logger.log(image.get('file'), type="image")
|
|
||||||
#report image
|
|
||||||
reporter.send(image.get('file'), type="image")
|
|
||||||
pass
|
|
||||||
|
|
||||||
def scheduler (interval, worker_func, iterations = 0):
|
|
||||||
if iterations != 1:
|
|
||||||
threading.Timer (
|
|
||||||
interval,
|
|
||||||
scheduler, [interval, worker_func, 0 if iterations == 0 else iterations-1]
|
|
||||||
).start ()
|
|
||||||
|
|
||||||
worker_func ()
|
|
||||||
|
|
||||||
#while 1:
|
|
||||||
|
|
||||||
update_camera()
|
|
||||||
#todo break apart log and report refresh
|
|
||||||
scheduler(refresh_barometer_local, update_barometer, 20)
|
|
||||||
scheduler(refresh_camera_local, update_camera, 1)
|
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|||||||
43
mission.py
Normal file
43
mission.py
Normal 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
28
post_encode.py
Normal 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
|
||||||
52
system.py
52
system.py
@@ -1,6 +1,9 @@
|
|||||||
__author__ = 'asc'
|
__author__ = 'asc'
|
||||||
|
import os
|
||||||
|
import psutil
|
||||||
|
|
||||||
class System_Debug():
|
|
||||||
|
class System():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
#TODO add system initialization
|
#TODO add system initialization
|
||||||
@@ -9,4 +12,49 @@ class System_Debug():
|
|||||||
@property
|
@property
|
||||||
def status (self):
|
def status (self):
|
||||||
#TODO status check
|
#TODO status check
|
||||||
return (0, "System functioning properly")
|
return (0, "System functioning properly")
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Return CPU temperature as a character string
|
||||||
|
def getCPUtemperature(self):
|
||||||
|
res = os.popen('vcgencmd measure_temp').readline()
|
||||||
|
return(res.replace("temp=","").replace("'C\n",""))
|
||||||
|
|
||||||
|
def getCPUusage(self):
|
||||||
|
p=psutil.cpu_percent(interval=1)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def getRAMinfo(self):
|
||||||
|
p = dict(psutil.virtual_memory()._asdict())
|
||||||
|
p = dict(psutil.swap_memory()._asdict())
|
||||||
|
return p
|
||||||
|
|
||||||
|
def getDiskSpace(self):
|
||||||
|
p = dict(psutil.disk_usage('/')._asdict())
|
||||||
|
return p
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stats (self):
|
||||||
|
# CPU informatiom
|
||||||
|
CPU_temp = self.getCPUtemperature()
|
||||||
|
CPU_usage = self.getCPUusage()
|
||||||
|
|
||||||
|
# RAM information
|
||||||
|
# Output is in kb, here I convert it in Mb for readability
|
||||||
|
RAM_stats = self.getRAMinfo()
|
||||||
|
|
||||||
|
|
||||||
|
# Disk information
|
||||||
|
DISK_stats = self.getDiskSpace()
|
||||||
|
|
||||||
|
pid = os.getpid()
|
||||||
|
|
||||||
|
|
||||||
|
# print(type(DISK_stats._asdict()))
|
||||||
|
|
||||||
|
return {"cpu_temp":CPU_temp,
|
||||||
|
"cpu_usage":CPU_usage,
|
||||||
|
"ram_stats":RAM_stats,
|
||||||
|
"disk_stats":DISK_stats,
|
||||||
|
"PID":pid}
|
||||||
115
test.py
115
test.py
@@ -1,21 +1,110 @@
|
|||||||
import csv, requests
|
__author__ = 'asc'
|
||||||
from instruments import Camera_Debug as Camera
|
DEBUG = True
|
||||||
from datahandling import Datareporter_Debug2 as Datareporter
|
import logging
|
||||||
|
import logging.handlers,logging.config
|
||||||
|
|
||||||
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')
|
||||||
|
|
||||||
data = {"temp":1,"press":3,"altitude":2,"cheetas":"just enough"}
|
log = logging.getLogger(__name__)
|
||||||
|
# log = logging.basicConfig()
|
||||||
|
# log1 = logging.getLogger("instruments")
|
||||||
|
# log2 = logging.getLogger("datahandling")
|
||||||
|
# handler = logging.handlers.RotatingFileHandler('spaceballoon.log', backupCount=5)
|
||||||
|
|
||||||
camera = Camera ()
|
formatter = logging.Formatter('[%(asctime)-25s] %(name)-15s %(levelname)-8s %(message)s')
|
||||||
reporter = Datareporter ()
|
# handler.setFormatter(formatter)
|
||||||
|
# log.addHandler(handler)
|
||||||
|
# log1.addHandler(handler)
|
||||||
|
# log2.addHandler(handler)
|
||||||
|
log.setLevel(logging.DEBUG)
|
||||||
|
# log1.setLevel(logging.DEBUG)
|
||||||
|
# log2.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
#start-up
|
||||||
|
#log denotes write to local, report denotes sending to server
|
||||||
|
|
||||||
|
#loadconfig
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
mission = Mission()
|
||||||
|
|
||||||
|
#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
|
||||||
|
)
|
||||||
|
|
||||||
|
#intiate mission
|
||||||
|
|
||||||
|
|
||||||
|
#TODO test camera
|
||||||
|
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()
|
||||||
|
|
||||||
response = requests.post(REPORTIMAGETOURL, files={'file': image.get('file')})
|
#todo test GPS, log, report
|
||||||
#report image
|
|
||||||
reporter.send(image.get('file'), type="image")
|
#todo check for errors, throw exception if error
|
||||||
pass
|
|
||||||
|
def scheduler (interval, worker_func, iterations = 0):
|
||||||
|
if iterations != 1:
|
||||||
|
threading.Timer (
|
||||||
|
interval,
|
||||||
|
scheduler, [interval, worker_func, 0 if iterations == 0 else iterations-1]
|
||||||
|
).start ()
|
||||||
|
|
||||||
|
worker_func ()
|
||||||
|
|
||||||
|
|
||||||
|
def update_barometer_local():
|
||||||
|
global bar
|
||||||
|
bar = barometer.read()
|
||||||
|
bar.update({'mt':mission.now()})
|
||||||
|
record = Record(bar.copy(),'b')
|
||||||
|
# notebook.log(record)
|
||||||
|
|
||||||
|
def update_image_local():
|
||||||
|
global img
|
||||||
|
img = camera.capture(name=mission.now(), thumbnail=None)
|
||||||
|
record = Record([img.get('hi'), img.get('lo')], 'i')
|
||||||
|
# notebook.log(record)
|
||||||
|
|
||||||
|
transpondence = Record()
|
||||||
|
|
||||||
|
update_image_local()
|
||||||
|
|
||||||
|
transpondence.add(img.get('lo'),'i')
|
||||||
|
transpondence = reporter.send(transpondence) #returns none on success, (bad practice?)
|
||||||
|
|
||||||
|
counter=1
|
||||||
|
|
||||||
|
img = None
|
||||||
|
bar = None
|
||||||
|
transpondence = None
|
||||||
|
|
||||||
|
pass
|
||||||
|
|||||||
89
test_http.py
Normal file
89
test_http.py
Normal 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
88
test_http_2.py
Normal 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")
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
__author__ = 'asc'
|
|
||||||
|
|
||||||
import json
|
|
||||||
from urllib import request
|
|
||||||
|
|
||||||
|
|
||||||
req = request.Request("http://home.ascorrea.com:5010/retrieve-report")
|
|
||||||
# req.add_header('Content-Type', 'application/json')
|
|
||||||
response = request.urlopen(req)
|
|
||||||
r = response.read()
|
|
||||||
json.loads(r.decode()).get('data')
|
|
||||||
pass
|
|
||||||
Reference in New Issue
Block a user