From 5e9b03a6511dc634e9919cdca2fde1f16bfc1839 Mon Sep 17 00:00:00 2001 From: Anthony Correa Date: Sat, 31 Oct 2015 00:00:00 -0600 Subject: [PATCH] commit 10/31/2015 --- config.ini | 32 +- config.py | 35 ++ datahandling.py | 366 +++++++++++------ instruments.py | 44 +- lib/sim900.bak/__init__.py | 0 lib/sim900.bak/__stand_alone__.py | 0 lib/sim900.bak/amsharedmini.py | 60 +++ lib/sim900.bak/gsm.py | 661 ++++++++++++++++++++++++++++++ lib/sim900.bak/imei.py | 45 ++ lib/sim900.bak/inetgsm.py | 622 ++++++++++++++++++++++++++++ lib/sim900.bak/simshared.py | 62 +++ lib/sim900.bak/smshandler.py | 653 +++++++++++++++++++++++++++++ lib/sim900.bak/ussdhandler.py | 126 ++++++ lib/sim900/gsm.py | 36 +- lib/sim900/inetgsm.py | 286 +++++++------ main.py | 135 +++--- post_encode.py | 28 ++ system.py | 24 +- test.py | 112 +++-- test_http.py | 89 ++++ test_http_2.py | 88 ++++ 21 files changed, 3109 insertions(+), 395 deletions(-) create mode 100644 config.py create mode 100644 lib/sim900.bak/__init__.py create mode 100644 lib/sim900.bak/__stand_alone__.py create mode 100644 lib/sim900.bak/amsharedmini.py create mode 100644 lib/sim900.bak/gsm.py create mode 100644 lib/sim900.bak/imei.py create mode 100644 lib/sim900.bak/inetgsm.py create mode 100644 lib/sim900.bak/simshared.py create mode 100644 lib/sim900.bak/smshandler.py create mode 100644 lib/sim900.bak/ussdhandler.py create mode 100644 post_encode.py create mode 100644 test_http.py create mode 100644 test_http_2.py diff --git a/config.ini b/config.ini index d10c58c..ad51c35 100644 --- a/config.ini +++ b/config.ini @@ -1,21 +1,33 @@ [refresh rates] -refresh camera local = 2 -refresh camera transmit = 5 +refresh camera local = 5 +refresh camera transmit = 10 -refresh barometer local = 10 +refresh barometer local = 1 refresh barometer transmit = 5 refresh gps local = 2 refresh gps transmit = 5 -[camera settings] -low quality resolution = (640, 360) -low quality compression pct = 20 +refresh system = 10 -[report settings] -report url = "http://10.0.1.4:5010/report" -report image url = "http://10.0.1.4:5010/photo" +[camera settings] +low quality resolution = (320, 240) +low quality compression pct = 10 +high quality resolution = (2592,1944) +high quality compression pct = 100 [modem settings] com port name = "/dev/ttyAMA0" -baud rate = "9600" \ No newline at end of file +baud rate = 9600 + +[server settings] +use_lan = True +url = "http://home.ascorrea.com" +server_port = 5010 +data_path = "upload-data" +image_path = "upload-file" +ping_path = "ping" + +[local storage settings] +photo path = /home/pi/scripts/spaceballoon/photos +log path = /home/pi/scripts/spaceballoon/logs diff --git a/config.py b/config.py new file mode 100644 index 0000000..7829965 --- /dev/null +++ b/config.py @@ -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'] diff --git a/datahandling.py b/datahandling.py index 9945a5e..711c296 100644 --- a/datahandling.py +++ b/datahandling.py @@ -6,28 +6,124 @@ import requests import json from cell_connection import initializeUartPort, baseOperations, initializeLogs import logging +from requests_toolbelt import MultipartEncoder +import post_encode +import base64 +import binascii +import array +import csv # logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) -LOGGER_LEVEL=logging.DEBUG -CONSOLE_LOGGER_LEVEL=logging.DEBUG +# LOGGER_LEVEL=logging.DEBUG +# CONSOLE_LOGGER_LEVEL=logging.DEBUG +# SCRIPTDIR = os.path.dirname(os.path.abspath(__file__)) +logger = logging.getLogger(__name__) +# handler = logging.StreamHandler() +# formatter = logging.Formatter('[%(asctime)-15s] %(name)-5s %(levelname)-8s %(message)s') +# handler.setFormatter(formatter) +# logger.addHandler(handler) +# logger.setLevel(logging.INFO) + +# logger_gsm = logging.getLogger("cell_connection") +# handler = logging.StreamHandler() +# formatter = logging.Formatter('[%(asctime)-15s] %(name)-5s %(levelname)-8s %(message)s') +# handler.setFormatter(formatter) +# logger_gsm.addHandler(handler) +# logger_gsm.setLevel(logging.DEBUG) + +# logger_1 = logging.getLogger("gsm") +# handler = logging.StreamHandler() +# formatter = logging.Formatter('[%(asctime)-15s] %(name)-5s %(levelname)-8s %(message)s') +# handler.setFormatter(formatter) +# logger_1.addHandler(handler) +# logger_1.setLevel(logging.DEBUG) +# +# logger_2 = logging.getLogger("intetgsm") +# handler = logging.StreamHandler() +# formatter = logging.Formatter('[%(asctime)-15s] %(name)-5s %(levelname)-8s %(message)s') +# handler.setFormatter(formatter) +# logger_2.addHandler(handler) +# logger_2.setLevel(logging.DEBUG) + +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_Debug (): - def __init__(self, path=SCRIPTDIR): + def __init__(self, text_path, + log_path, + photo_path): + self.text_path=text_path + self.log_path=os.path.join(log_path, datetime.datetime.now().strftime("Log %Y%m%d-%H%M%S.csv")) + self.photo_path=photo_path pass - def log(self, message, type="text"): - if type == "text": - print ("%s - Log Message: %s"% (str(datetime.datetime.now()), message)) - elif type == "image": - print ("%s - Log Image: %s"% (str(datetime.datetime.now()), message)) - elif type == "data": + + + 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 @@ -58,7 +154,7 @@ class Datareporter_Debug2 (): report_url = "http://10.0.1.4:5010/report", report_image_url = "http://10.0.1.4:5010/photo", com_port_name = "/dev/ttyAMA0", - baud_rate = "9600"): + baud_rate = 9600): self.report_url = report_url self.report_image_url = report_image_url self.com_port_name = com_port_name, @@ -106,126 +202,168 @@ class Datareporter_Debug3 (): #Debug 2 sends from cell to server from lib.sim900.inetgsm import SimInetGSM - def __init__(self, path=SCRIPTDIR, - report_url = "http://10.0.1.4:5010/report", - report_image_url = "http://10.0.1.4:5010/upload-file", - com_port_name = "/dev/ttyAMA0", - baud_rate = "9600", - content_type = None): + def __init__(self, + url, + data_path, + image_path, + ping_path, + server_port, + com_port_name=None, + baud_rate=None, + use_lan = False, + path=SCRIPTDIR): #TODO communication - self.report_url = report_url - self.report_image_url = report_image_url + self.url = url + self.server_port=server_port + self.image_path = image_path + self.data_path = data_path self.com_port_name = com_port_name self.baud_rate = baud_rate - self.content_type = content_type - + self.ping_path = ping_path + self.use_lan = use_lan + self.port = initializeUartPort(portName=self.com_port_name, baudrate=self.baud_rate) + if not use_lan: + d = baseOperations(self.port, logger) + + if not d is None: + # return None + + (self.gsm, self.imei) = d + + self.inet = self.SimInetGSM(self.port, logger) + + logger.info("ip = {0}".format(self.inet.ip)) + pass @property def status (self): #TODO status check try: - check = json.loads(request.urlopen(self.report_url).read().decode()).get('status') - pass - if not check: - return (0, "Data reporter functioning properly") - else: - return (1, check) + + return (0, "Data reporter functioning properly") + except Exception as e: return (1, "Data reporter error: %s" % e) - def send(self, message, type="text"): - if type == "text": + def send(self, message, message_type): + # logger.debug("Message.read is {}".format(message.read())) + if message_type == "ping": #TODO send text - print ("%s - Sent Message: %s"% (str(datetime.datetime.now()), message)) - elif type == "image": - #todo send image - # response = requests.post(self.report_image_url, files={'file': message}) - print ("%s - Sent Image: %s"% (str(datetime.datetime.now()), message)) + contentType='text/xml' + + if self.use_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("making HTTP POST request from cell") + logger.info("attaching GPRS") + + if not self.inet.attachGPRS("wholesale", "", "", 1): + logger.error("error attaching GPRS") + return False + + if not self.inet.httpPOST( + self.url, + self.server_port, + "/{}".format(self.ping_path), + json.dumps(message), + contentType=contentType + ): + logger.error("error making HTTP GET 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": + contentType="mulipart/form-data" + re=message + response=None + m = MultipartEncoder(fields={'image': ('image', message, 'image/jpeg')}) + + + if self.use_lan: + # req.add_header('Content-Type', contentType) + response = requests.post("{0}:{1}/{2}".format(self.url, self.server_port, self.image_path), data=m.read(), headers={'Content-Type': m.content_type}) + # response = requests.post("{0}:{1}/{2}".format(self.url, self.server_port, self.image_path), data=m) + # response = response.text() + pass + + elif not self.use_lan: + logger.info("attaching GPRS") + if not self.inet.attachGPRS("wholesale", "", "", 1): + logger.error("error attaching GPRS") + return False + + logger.info("ip = {0}".format(self.inet.ip)) + + #making HTTP GET request + logger.info("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" - #adding & initializing port object - port = initializeUartPort(portName=self.com_port_name, baudrate=self.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 = self.SimInetGSM(port, logger) - - logger.info("attaching GPRS") - if not inet.attachGPRS("wholesale", "", "", 1): - logger.error("error attaching GPRS") - return False - - logger.info("ip = {0}".format(inet.ip)) - + elif message_type == "data": #making HTTP GET request - logger.info("making HTTP POST request") + contentType="application/json" + message["sent"] = str(datetime.datetime.now()) - if not inet.httpPOST_DATA( - "home.ascorrea.com", - 5010, - "/upload-file", - # content=self.content_type, - parameters="{0}".format(message) - ): - logger.error("error making HTTP GET post: {0}".format(inet.errorText)) - return False + if self.use_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 - logger.info("httpResult = {0}".format(inet.httpResult)) - if inet.httpResponse is not None: - response = str(inet.httpResponse).replace("\n\r", "\n") + elif not self.use_lan: + logger.info("making HTTP POST request from cell") + logger.info("attaching GPRS") + + if not self.inet.attachGPRS("wholesale", "", "", 1): + logger.error("error attaching GPRS") + return False + + if not self.inet.httpPOST( + self.url, + self.server_port, + "/{}".format(self.data_path), + json.dumps(message), + contentType=contentType + ): + logger.error("error making HTTP GET post: {0}".format(self.inet.errorText)) + return False + + if response is not None: + response = str(self.inet.httpResponse).replace("\n\r", "\n") + else: + response = ("empty response") + + if response is not None: logger.info("response: \"{0}\"".format(response)) - else: - logger.info("empty response") - - elif type == "data": - #adding & initializing port object - port = initializeUartPort(portName=self.com_port_name, baudrate=self.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 = self.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 POST request") - - - - if not inet.httpPOST( - "home.ascorrea.com", - 5010, - "/report-encoded", - parse.urlencode(message) - ): - logger.error("error making HTTP GET post: {0}".format(inet.errorText)) - return False - - logger.info("httpResult = {0}".format(inet.httpResult)) - if inet.httpResponse is not None: - response = str(inet.httpResponse).replace("\n\r", "\n") - logger.info("response: \"{0}\"".format(response)) - else: + else: logger.info("empty response") + diff --git a/instruments.py b/instruments.py index 544db7f..559bbca 100644 --- a/instruments.py +++ b/instruments.py @@ -65,15 +65,24 @@ class Camera_Debug(): #camera debug 2 uses actual camera class Camera_Debug2(): - def __init__(self, low_quality_resolution, - low_quality_compression_pct, - high_quality_resolution=None, - high_qualitycompression_pct=85): + def __init__(self, low_quality_resolution=(320,240), + low_quality_compression_pct=5, + high_quality_resolution=(2592,1944), + high_quality_compression_pct=85, + **kwargs): logger.debug("Initializing camera") time.sleep(1) self.low_quality_resolution = low_quality_resolution self.low_quality_compression_pct = low_quality_compression_pct - self._cam = picamera.PiCamera() + self.high_quality_resolution = high_quality_resolution + self.high_quality_compression_pct = high_quality_compression_pct + self.kwargs=kwargs + self._cam = picamera.PiCamera(resolution=high_quality_resolution) + # if "vflip" in kwargs.keys(): + for k in kwargs.keys(): + setattr(self._cam, k, kwargs.get(k)) + + logger.debug("Camera intialized") pass @@ -81,22 +90,33 @@ class Camera_Debug2(): def status (self): return (0, "Camera functioning properly") - def capture (self, no_low_quality=False, no_high_quality=False): + def capture (self, no_low_quality=False, no_high_quality=False, **kwargs): #todo image adjustments img_hi = None img_lo = None if not no_high_quality: logger.debug('Taking high quality photo') - self._cam.capture("temp_img_hi.jpg") + self._cam.capture("temp_img_hi.jpg", + resize=self.high_quality_resolution, + quality=self.high_quality_compression_pct, + # **kwargs + ) img_hi = open("temp_img_hi.jpg", 'rb') - logger.debug('High quality photo taken, file: {}'.format(img_hi)) + with open("temp_img_hi.jpg", 'rb') as f: + img_hi = f.read() + logger.debug('High quality photo taken') + if not no_low_quality: - logger.debug('Taking low quality photo (Resolution: {}, JPEG Quality: {}%'.format(self.low_quality_resolution, self.low_quality_compression_pct)) + time.sleep(1) + logger.debug('Taking low quality photo (Resolution: {}, JPEG Quality: {}%)'.format(self.low_quality_resolution, self.low_quality_compression_pct)) self._cam.capture("temp_img_lo.jpg", resize=self.low_quality_resolution, - quality=self.low_quality_compression_pct) - img_lo = open("temp_img_lo.jpg", 'rb') - logger.debug('Low quality photo taken, file: {}'.format(img_lo)) + quality=self.low_quality_compression_pct, + # **kwargs + ) + with open("temp_img_lo.jpg", 'rb') as f: + img_lo = f.read() + logger.debug('Low quality photo taken') return ({"hi":img_hi, "lo":img_lo}) diff --git a/lib/sim900.bak/__init__.py b/lib/sim900.bak/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/sim900.bak/__stand_alone__.py b/lib/sim900.bak/__stand_alone__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/sim900.bak/amsharedmini.py b/lib/sim900.bak/amsharedmini.py new file mode 100644 index 0000000..037510a --- /dev/null +++ b/lib/sim900.bak/amsharedmini.py @@ -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 \ No newline at end of file diff --git a/lib/sim900.bak/gsm.py b/lib/sim900.bak/gsm.py new file mode 100644 index 0000000..d0c65b2 --- /dev/null +++ b/lib/sim900.bak/gsm.py @@ -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)) diff --git a/lib/sim900.bak/imei.py b/lib/sim900.bak/imei.py new file mode 100644 index 0000000..343f93b --- /dev/null +++ b/lib/sim900.bak/imei.py @@ -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() \ No newline at end of file diff --git a/lib/sim900.bak/inetgsm.py b/lib/sim900.bak/inetgsm.py new file mode 100644 index 0000000..ad9dc86 --- /dev/null +++ b/lib/sim900.bak/inetgsm.py @@ -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; \ No newline at end of file diff --git a/lib/sim900.bak/simshared.py b/lib/sim900.bak/simshared.py new file mode 100644 index 0000000..6576ab7 --- /dev/null +++ b/lib/sim900.bak/simshared.py @@ -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 \ No newline at end of file diff --git a/lib/sim900.bak/smshandler.py b/lib/sim900.bak/smshandler.py new file mode 100644 index 0000000..97e3c0e --- /dev/null +++ b/lib/sim900.bak/smshandler.py @@ -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 diff --git a/lib/sim900.bak/ussdhandler.py b/lib/sim900.bak/ussdhandler.py new file mode 100644 index 0000000..b39023b --- /dev/null +++ b/lib/sim900.bak/ussdhandler.py @@ -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 + diff --git a/lib/sim900/gsm.py b/lib/sim900/gsm.py index a831585..6b57f57 100644 --- a/lib/sim900/gsm.py +++ b/lib/sim900/gsm.py @@ -33,7 +33,6 @@ import time import serial import logging from lib.sim900.simshared import * -import struct class GsmSpecialCharacters: ctrlz = 26 #//Ascii character for ctr+z. End of a SMS. @@ -133,7 +132,7 @@ class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging): """ return self.print(commandLine, encoding) - def printLn(self, commandString, bytes=None, encoding = "ascii"): + def printLn(self, commandString, encoding = "ascii"): """ Sends string data and CR/LF in the end to the SIM module @@ -141,16 +140,33 @@ class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging): :param encoding: before sending string it will be converted to the bytearray with this encoding :return: True if data sent, otherwise returns False """ - if bytes is not None: - data = bytearray(commandString, encoding) + bytes + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf]) + if not isinstance(commandString, bytes): + data = bytearray(commandString, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf]) else: - data= bytearray(commandString, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf]) - - # print("Print Line data: {}".format(data)) - + data = commandString + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf]) return self.__sendRawBytes(data) - def simpleWriteLn(self, commandLine, bytes=None, encoding = "ascii"): + def simpleWriteLns(self, commandLines, encoding = "ascii"): + for i, l in enumerate(commandLines): + + if i < len(commandLines)-1: + if not isinstance(l, bytes): + data = bytearray(l, encoding) + else: + data = l + else: + if not isinstance(l, bytes): + data = bytearray(l, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf]) + else: + data = l + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf]) + + r = self.__sendRawBytes(data) + if r is False: + return r + + return r + + def simpleWriteLn(self, commandLine, encoding = "ascii"): """ Just alias for printLn() method @@ -159,7 +175,7 @@ class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging): :return: True if data sent, otherwise returns False """ - return self.printLn(commandLine, encoding=encoding, bytes=bytes) + return self.printLn(commandLine, encoding) def flushInput(self): """ diff --git a/lib/sim900/inetgsm.py b/lib/sim900/inetgsm.py index dbba653..f586a86 100644 --- a/lib/sim900/inetgsm.py +++ b/lib/sim900/inetgsm.py @@ -38,6 +38,10 @@ class SimInetGSMConnection: 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) @@ -172,13 +176,54 @@ class SimInetGSM(SimGsm): #returning GPRS checking sequence return self.checkGprsBearer() - def disconnectTcp(self): - """ - Disconnects TCP connection - :return: - """ + def attachCIP(self, apn, user=None, password=None, bearerNumber = 1): + """ + Attaches GPRS connection for SIM module - return self.commandAndStdResult("AT+CIPCLOSE", 1000, ["OK"]) + :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): """ @@ -409,7 +454,7 @@ class SimInetGSM(SimGsm): self.__httpResponse = None self.__httpResult = 0 - def httpPOST(self, server, port, path, parameters, bearerChannel = 1): + def httpPOST(self, server, port, path, parameters, contentType="application/x-www-form-urlencoded", bearerChannel = 1): """ Makes HTTP POST request to the given server and script @@ -431,10 +476,10 @@ class SimInetGSM(SimGsm): [ "AT+HTTPINIT", 2000 ], [ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ], [ "AT+HTTPPARA=\"URL\",\"{0}:{1}{2}\"".format(server, port, path), 500 ], - [ "AT+HTTPPARA=\"CONTENT\",\"application/x-www-form-urlencoded\"", 500 ], + [ "AT+HTTPPARA=\"CONTENT\",\"{0}\"".format(contentType), 500 ], [ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ], [ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ], - [ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ] + [ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ], ] #executing commands sequence @@ -445,7 +490,7 @@ class SimInetGSM(SimGsm): #uploading data self.logger.debug("uploading HTTP POST data") ret = self.commandAndStdResult( - "AT+HTTPDATA={0},10000".format(len(parameters)), + "AT+HTTPDATA={0},60000".format(len(parameters)), 7000, ["DOWNLOAD", "ERROR"] ) @@ -456,9 +501,14 @@ class SimInetGSM(SimGsm): self.simpleWriteLn(parameters) - dataLine = self.readDataLine(500) + # if len(parameters)<32: + # self.simpleWriteLn(parameters) + # else: + # parameters = self.simpleWriteLns(chunks(parameters,32)) + + dataLine = self.readDataLine(maxWaitTime=20000) if (dataLine is None) or (dataLine != "OK"): - self.setError("{0}: can't upload HTTP POST data".format(inspect.stack()[0][3])) + self.setError("{0}: can't upload HTTP POST data {1}".format(inspect.stack()[0][3], dataLine)) return self.logger.debug("actually making request") @@ -512,121 +562,115 @@ class SimInetGSM(SimGsm): # # return True - def httpPOST_DATA(self, server, port, path, parameters, bearerChannel = 1): - """ - Makes HTTP POST request to the given server and script + # + # int res= gsm.read(result, resultlength); + # //gsm.disconnectTCP(); + # return res; - :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 - """ + 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 - # self.__clearHttpResponse() + :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 + """ - #TODO: close only when opened - # self.terminateHttpRequest() + self.__clearHttpResponse() - #HTTP POST request commands sequence - # simpleCommands = [ - # [ "AT+HTTPINIT", 2000 ], - # [ "AT+HTTPPARA=\"CID\",\"{0}\"".format(bearerChannel), 1000 ], - # [ "AT+HTTPPARA=\"URL\",\"{0}:{1}{2}\"".format(server, port, path), 500 ], - # [ "AT+HTTPPARA=\"CONTENT\",\"multipart/form-data\"", 500 ], - # [ "AT+HTTPPARA=\"UA\",\"{0}\"".format(self.userAgent), 500 ], - # [ "AT+HTTPPARA=\"REDIR\",\"1\"", 500 ], - # [ "AT+HTTPPARA=\"TIMEOUT\",\"45\"", 500 ] - # ] - - simpleCommands = [ - [ "AT+CGATT?", 2000 ], - [ "AT+CIPCLOSE", 1000 ], - # [ "AT+CIPMUX=0", 1000 ], - [ "AT+CSTT=\"{0}\"".format('wholesale'), 1000], - [ "AT+CIICR", 500 ], - [ "AT+CIFSR", 500 ], - [ "AT+CIPSTART=\"{0}\",\"{1}\",\"{2}\"".format("TCP", server, port), 500 ], - [ "AT+CIPSEND", 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): + #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 - #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.disconnectTcp() + # + # 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; \ No newline at end of file + # + # int res= gsm.read(result, resultlength); + # //gsm.disconnectTCP(); + # return res; \ No newline at end of file diff --git a/main.py b/main.py index 13b7bbd..5fb92f1 100644 --- a/main.py +++ b/main.py @@ -6,9 +6,10 @@ if DEBUG: from instruments import Barometer_Debug2 as Barometer from instruments import Camera_Debug2 as Camera from datahandling import Datalogger_Debug as Datalogger - from datahandling import Datareporter_Debug2 as Datareporter + from datahandling import Datareporter_Debug3 as Datareporter from system import System_Debug as System import threading + import datetime import configparser @@ -19,13 +20,19 @@ import configparser # log. # # log.setFo -log = logging.getLogger("instruments") -handler = logging.StreamHandler() +log = logging.getLogger(__name__) +log1 = logging.getLogger("instruments") +log2 = logging.getLogger("datahandling") +handler = logging.FileHandler('myapp.log') -formatter = logging.Formatter('%(asctime)-15s %(name)-5s %(levelname)-8s %(message)s') +formatter = logging.Formatter('[%(asctime)-15s] %(name)-15s %(levelname)-8s %(message)s') 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 @@ -33,62 +40,48 @@ log.setLevel(logging.DEBUG) #TODO startup message #loadconfig -config = configparser.ConfigParser() -config.sections() -config.read('config.ini') -refresh_camera_local = float(config['refresh rates']['refresh camera local']) -refresh_camera_transmit = float(config['refresh rates']['refresh camera transmit']) +from config import * -refresh_barometer_local = float(config['refresh rates']['refresh barometer local']) -refresh_barometer_transmit = float(config['refresh rates']['refresh barometer transmit']) - -refresh_gps_local = float(config['refresh rates']['refresh gps local']) -refresh_gps_transmit = float(config['refresh rates']['refresh gps transmit']) - -low_quality_resolution = eval(config['camera settings']['low quality resolution']) -low_quality_compression_pct = int(config['camera settings']['low quality compression pct']) - -report_url = config['report settings']['report url'] -report_image_url = config['report settings']['report image url'] - -com_port_name = config['modem settings']['com port name'] -baud_rate = config['modem settings']['baud rate'] - -logger = Datalogger () +notebook = Datalogger (text_path=None, + log_path=log_path, + photo_path=photo_path) log.debug ("System started") # logger.log("Log Intiated") #system check system = System() -system_status = system.status -# logger.log (system_status[1]) +log.debug (system.stats) -#todo test cell connection, log, report -reporter = Datareporter() -reporter_status = reporter.status -# logger.log (reporter_status[1]) -reporter.send(reporter_status[1]) -reporter.send(system_status[1]) +#todo test cell connection +reporter = Datareporter ( + use_lan = True, + url = "http://home.ascorrea.com", + server_port = 5010, + data_path = "upload-data", + image_path = "upload-file", + ping_path = "ping", + com_port_name="/dev/ttyAMA0", + baud_rate = 9600) -#TODO test camera, log, report +#TODO test camera camera = Camera (low_quality_compression_pct=low_quality_compression_pct, - low_quality_resolution=low_quality_resolution) -camera_status = camera.status + low_quality_resolution=low_quality_resolution, + high_quality_compression_pct=high_quality_compression_pct, + high_quality_resolution=high_quality_resolution, + vflip=True, + hflip=True, + exposure_mode='sports' + ) -#todo test barometer, log, report + +#todo test barometer barometer = Barometer() -# barometer_status = barometer.status -# logger.log (barometer_status[1]) -# reporter.send(barometer_status[1]) #todo test GPS, log, report #todo check for errors, throw exception if error -if(system_status[0] or reporter_status[0] or camera_status[0]): - raise Exception ('Error') - def scheduler (interval, worker_func, iterations = 0): if iterations != 1: threading.Timer ( @@ -98,19 +91,55 @@ def scheduler (interval, worker_func, iterations = 0): worker_func () -#while 1: -image = camera.capture() -data = barometer.read() +def update_barometer_local(): + global bar + bar = barometer.read() + notebook.log(bar, message_type="data") -# logger.log(image.get('hi'), type='image') -# logger.log(barometer, type='data') -reporter.send(image.get('lo'), type='data') -reporter.send(barometer, type = 'data') +def update_image_local(): + global img + img = camera.capture() + notebook.log(img.get('hi'), message_type="image") -#todo break apart log and report refresh -# scheduler(refresh_barometer_local, update_barometer, 2) -# scheduler(refresh_camera_local, update_camera, 2) +scheduler(2, update_barometer_local) +scheduler(5, update_image_local) + +counter=1 +while counter>0: + global img + global bar + # bar=None + # img=None + + try: + reporter.send(1, message_type="ping") + + # if (counter % refresh_camera_local) == 0: + # if not img: + # img = camera.capture() + # notebook.log(img.get('hi'), message_type="image") + + if (counter % refresh_barometer_transmit) == 0: + if not bar: + bar = barometer.read() + reporter.send(bar, message_type="data") + + if (counter % refresh_camera_transmit) == 0: + # log.debug('Need to transmit picture, {}'.format(img)) + if not img: + img = camera.capture() + reporter.send(img.get('lo'), message_type="image") + + if (counter % refresh_system) == 0: + log.debug(system.stats) + + except Exception as e: + log.debug("Exception: {}".format(e)) + continue + + counter += 1 + log.debug("Counter = {}".format(counter)) pass diff --git a/post_encode.py b/post_encode.py new file mode 100644 index 0000000..77262a3 --- /dev/null +++ b/post_encode.py @@ -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 \ No newline at end of file diff --git a/system.py b/system.py index c671657..aceb707 100644 --- a/system.py +++ b/system.py @@ -17,7 +17,7 @@ class System_Debug(): import os # Return CPU temperature as a character string - def getCPUtemperature(): + def getCPUtemperature(self): res = os.popen('vcgencmd measure_temp').readline() return(res.replace("temp=","").replace("'C\n","")) @@ -25,22 +25,20 @@ class System_Debug(): p=psutil.cpu_percent(interval=1) return p - def getRAMinfo(): - p = psutil.virtual_memory() - p = psutil.swap_memory() + def getRAMinfo(self): + p = dict(psutil.virtual_memory()._asdict()) + p = dict(psutil.swap_memory()._asdict()) return p - def getDiskSpace(): - p = psutil.disk_usage('/') + def getDiskSpace(self): + p = dict(psutil.disk_usage('/')._asdict()) return p @property def stats (self): - #courtesy of Phillipe - # https://www.raspberrypi.org/forums/memberlist.php?mode=viewprofile&u=40834&sid=dd38cc12161ac10b324ed2a2238972d3 # CPU informatiom CPU_temp = self.getCPUtemperature() - CPU_usage = self.getCPUuse() + CPU_usage = self.getCPUusage() # RAM information # Output is in kb, here I convert it in Mb for readability @@ -50,7 +48,13 @@ class System_Debug(): # 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} \ No newline at end of file + "disk_stats":DISK_stats, + "PID":pid} \ No newline at end of file diff --git a/test.py b/test.py index d354f98..dd97266 100644 --- a/test.py +++ b/test.py @@ -1,80 +1,62 @@ import csv, requests from instruments import Camera_Debug2 as Camera from datahandling import Datareporter_Debug3 as Datareporter +from system import System_Debug import serial import sys -import json - -REPORTIMAGETOURL = "http://10.0.1.4:5010/photo" - -LOG_FILE = "log2.txt" - -import binascii - +import logging import base64 -from base64 import urlsafe_b64encode +from requests_toolbelt import MultipartEncoder -with open('log2.txt','rb') as f: - g=f.read() - e=urlsafe_b64encode(g) +logging.getLogger("datahandling") -# a=binascii.b2a_uu(f) - -# e = f.encode('base64') - -# e = str(e, "ascii") - -data = {"temp":1, - "press":3, - "altitude":2, - "image":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - } - -# data = json.dumps(data,ensure_ascii=True) - -# camera = Camera(low_quality_resolution=(320, 180), -# low_quality_compression_pct=50) - -# image = camera.capture() - -# print (image) +data = {"temp":1,"press":3,"altitude":2,"cheetas":"just enough"} pass -from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor + +com_port_name = "/dev/ttyAMA0" + +image = open("resized_image.jpg", 'rb') +# image = image.read() + +# e=base64.b64encode(image) + +# e=b'alksdjnfkljanslkjnaklsfnglkanlkfgnlakalksdjnaklsfnglkajnaklsfnglkanlkfgnlakalksdjnfkljanslkjnglkjnaklsfnglkanlkfgnlakalksdjnfkljanslkjnglknlkfgnlakalksdjnfkljanslkjnglkanlkfgnlakalksdjnfkljanngnlakalksdjnfkljanslkjnglkanlkfgnlakalksdjnfkljannlkanlkfgnlakalksdjnfkljannglkanlkfgnlakalksdjnfkljannglkanlkfgnlakalksdjnfkl==' + +# m = MultipartEncoder(fields={'image': ('image', image, 'image/jpeg')}) + +report = Datareporter ( + use_lan = True, + url = "http://home.ascorrea.com", + server_port = 5010, + data_path = "upload-data", + image_path = "upload-file", + com_port_name="/dev/ttyAMA0", + ping_path="ping", + baud_rate = 9600) + +camera = Camera (low_quality_compression_pct=10, + low_quality_resolution=(320, 240), + high_quality_compression_pct=100, + high_quality_resolution=(2592,1944), + vflip=True, + hflip=True, + exposure_mode='sports' + ) + +# print (report.check()) +# print (image) +# report.send(m.to_string(), message_type="image") +# report.send(data, message_type="data") + +img = camera.capture() +report.send(img.get('lo'), message_type="image") + +# report.send(image, message_type="image") -# i=open('resized_image.jpg','rb') -# r=i.read() +# execfile("test_sms.py") -# def callback(encoder, bytes_read): -# # Do something with this information -# pass -# -# monitor = MultipartEncoderMonitor.from_fields( -# fields={'field0': 'value0'}, callback -# ) - - - -# m = MultipartEncoder(fields={"field":("image", open("resized_image.jpg", 'rb'), "image/jpeg")}) -# -reporter = Datareporter (content_type="form-data") -# -# f = open('resized_image.jpg','rb') -# r=f.read() - -#report image -# response = requests.post("http://home.ascorrea.com:5010/upload-file", data=m.read()) -# print(type(r)) -# reporter.send(str(r)[2:-5].replace("\\\", "\\"), type="image") -# reporter.send("Content-Disposition: {0}; name=\"{1}\"; filename=\"{2}\" {4}".format( -# 'application/octet-stream', -# 'field', -# 'resized_image', -# 'image/jpeg',""), type="image") - - - -reporter.send(data, type="image") +# report.send("1",message_type="ping") pass \ No newline at end of file diff --git a/test_http.py b/test_http.py new file mode 100644 index 0000000..ebfd2af --- /dev/null +++ b/test_http.py @@ -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") diff --git a/test_http_2.py b/test_http_2.py new file mode 100644 index 0000000..021ac3a --- /dev/null +++ b/test_http_2.py @@ -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")