From 2309a0e031f1e2546eae80047a5325234fe9bf08 Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Thu, 20 Oct 2022 23:15:04 +0200 Subject: [PATCH 1/4] Improve Gammu SMS Receiver This as a rewrite of `gammu_get_unread_sms.py` script that adds: - Support for long SMS - Added proper CLI interface (see --help) - Added optional --delete parameter which deletes SMS after printing them as JSON - Added optional --show-read parameter which shows all not Unread marked SMS - Added logging and --debug option - Retain retrocompatibility with earlier versions of this script - Retain retrocompatibility with Python 2.7+ (hopefully) Fixes #181. Btw, the interface with RaspiSMS could be improved with a temporary JSON file, using stdout seems lossy. --- bin/gammu_get_unread_sms.py | 237 ++++++++++++++++++++++++++++++------ 1 file changed, 203 insertions(+), 34 deletions(-) diff --git a/bin/gammu_get_unread_sms.py b/bin/gammu_get_unread_sms.py index 39b493d..a8b0761 100755 --- a/bin/gammu_get_unread_sms.py +++ b/bin/gammu_get_unread_sms.py @@ -1,56 +1,225 @@ #!/usr/bin/env python3 # -*- coding: UTF-8 -*- # vim: expandtab sw=4 ts=4 sts=4: -# + +# (C) 2003 - 2018 Michal Čihař - python-gammu +# (C) 2015 - 2021 Raspian France - RaspianFrance/raspisms +# (C) 2022 Orsiris de Jong - NetInvent SASU + from __future__ import print_function + + +__intname__ = "gammu_get_unread_sms.py" +__author__ = "Orsiris de Jong - " +__version__ = "2.0.0" +__build__ = "2022102001" +__compat__ = "python2.7+" + + +import os import gammu -import sys import json +import logging +from logging.handlers import RotatingFileHandler +import tempfile +from argparse import ArgumentParser +import subprocess +import re -def main(): +LOG_FILE = "/var/log/{}.log".format(__intname__) +_DEBUG = os.environ.get("_DEBUG", False) +logger = logging.getLogger(__name__) + + +def get_logger(log_file): + try: + try: + filehandler = RotatingFileHandler( + log_file, mode="a", encoding="utf-8", maxBytes=1048576, backupCount=3 + ) + except OSError: + try: + temp_log_file = tempfile.gettempdir() + os.sep + __name__ + ".log" + filehandler = RotatingFileHandler( + temp_log_file, + mode="a", + encoding="utf-8", + maxBytes=1048576, + backupCount=3, + ) + except OSError as exc: + print("Cannot create log file: %s" % exc.__str__()) + return None + + _logger = logging.getLogger() + if _DEBUG: + _logger.setLevel(logging.DEBUG) + else: + _logger.setLevel(logging.INFO) + _logger.addHandler(filehandler) + _logger.addHandler(logging.StreamHandler()) + return _logger + except Exception as exc: + print("Cannot create logger instance: %s" % exc.__str__()) + + +def get_gammu_version(): + # Quite badly coded, i'd use command_runner but I try to not have dependencies here + try: + proc = subprocess.Popen( + ["LC_ALL=C gammu", "--version"], shell=True, stdout=subprocess.PIPE + ) + stdout, _ = proc.communicate() + version = re.search(r"Gammu version ([0-9]+)\.([0-9]+)\.([0-9]+)", str(stdout)) + # dont' bother to return version[0] since it's the whole match + return (int(version[1]), int(version[2]), int(version[3])) + except Exception as exc: + logger.error("Cannot get gammu version: %s" % exc.__str__()) + return None + + +def get_gammu_handle(config_file): state_machine = gammu.StateMachine() - - if len(sys.argv) < 2: - sys.exit(1) - else : - state_machine.ReadConfig(Filename=sys.argv[1]) - del sys.argv[1] + if config_file: + state_machine.ReadConfig(Filename=config_file) + else: + state_machine.Readconfig() state_machine.Init() + return state_machine + + +def load_sms_from_gammu(state_machine): + """ + The actual function that retrieves SMS via GAMMU from your modem / phone + Also concatenates multiple SMS into single long SMS + """ status = state_machine.GetSMSStatus() - remain = status['SIMUsed'] + status['PhoneUsed'] + status['TemplatesUsed'] - - start = True + remaining_sms = status["SIMUsed"] + status["PhoneUsed"] + status["TemplatesUsed"] + logger.debug("Found %s sms" % remaining_sms) + sms_list = [] try: - while remain > 0: - if start: - sms = state_machine.GetNextSMS(Start=True, Folder=0) - start = False + is_first_message = True + while remaining_sms > 0: + if is_first_message: + sms = state_machine.GetNextSMS(Start=is_first_message, Folder=0) + is_first_message = False else: - sms = state_machine.GetNextSMS( - Location=sms[0]['Location'], Folder=0 - ) - remain = remain - len(sms) + sms = state_machine.GetNextSMS(Location=sms[0]["Location"], Folder=0) + remaining_sms = remaining_sms - len(sms) + sms_list.append(sms) - for m in sms : - if m['State'] != 'UnRead' : - continue - - print(json.dumps({ - 'number': m['Number'], - 'at': str(m['DateTime']), - 'status': m['State'], - 'text': m['Text'], - })) + # Concat multiSMS into list of sms that go together except gammu.ERR_EMPTY: - #do noting - return True + logger.debug("Finished reading all messages") + + return gammu.LinkSMS(sms_list) -if __name__ == '__main__': - main() +def render_sms_as_json(state_machine, sms_list, delete_sms, show_read_sms): + """ + Provided sms_list is a list of lists of sms, eg + sms_list = [ + [sms], + [sms1, sms2], # When two sms are in the same list, they form a long sms + [sms], + ] + + Concatenate long SMS from multiple sends and print them as JSON on stdout + """ + + for sms in sms_list: + if sms[0]["State"] == "UnRead" or show_read_sms: + sms_text = "" + for to_concat_sms in sms: + sms_text += to_concat_sms["Text"] + print( + json.dumps( + { + "number": sms[0]["Number"], + "at": str(sms[0]["DateTime"]), + "status": sms[0]["State"], + "text": sms_text, + } + ) + ) + + if delete_sms: + for to_concat_sms in sms: + try: + state_machine.DeleteSMS( + to_concat_sms["Folder"], to_concat_sms["Location"] + ) + except Exception as exc: + logger.error("Cannot delete sms: %s" % exc.__str__()) + + +def main(config_file, delete_sms, show_read): + # type: (bool, bool) -> None + try: + # Mandatory modem config file + # config_file = sys.argv[1] + + state_machine = get_gammu_handle(config_file) + + sms_list = load_sms_from_gammu(state_machine) + render_sms_as_json(state_machine, sms_list, delete_sms, show_read) + + except Exception as exc: + logger.error("Could not retrieve SMS from Gammu: %s" % exc.__str__()) + logger.debug("Trace:", exc_info=True) + + +if __name__ == "__main__": + parser = ArgumentParser("Gammu SMS retriever") + parser.add_argument( + "gammu_config_file", type=str, nargs="?", help="Gammu config file" + ) + parser.add_argument("--debug", action="store_true", help="Activate debugging") + parser.add_argument( + "-l", + "--log-file", + type=str, + dest="log_file", + default=None, + help="Optional path to log file, defaults to /var/log", + ) + parser.add_argument( + "--delete", action="store_true", help="Delete messages after they've been read" + ) + parser.add_argument( + "--show-read", action="store_true", help="Also show already read messages" + ) + + args = parser.parse_args() + + config_file = args.gammu_config_file + + if args.log_file: + LOG_FILE = args.log_file + + if args.debug: + _DEBUG = args.debug + + _logger = get_logger(LOG_FILE) + if _logger: + logger = _logger + + delete = False + if args.delete: + # We need to check if we have gammu >= 1.42.0 since deleting sms with lower versions fail with: + # Cannot delete sms: {'Text': 'The type of memory is not available or has been disabled.', 'Where': 'DeleteSMS', 'Code': 81} + # see https://github.com/gammu/gammu/issues/460 + gammu_version = get_gammu_version() + if gammu_version[0] >= 1 and gammu_version[1] >= 42: + delete = True + else: + logger.warning("Cannot delete SMS. You need gammu >= 1.42.0.") + + show_read = args.show_read + main(config_file, delete, show_read) From 3c8061dbbb866bdc6fce7e61d977747fdb2d956a Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Sun, 23 Oct 2022 11:19:22 +0200 Subject: [PATCH 2/4] Allow get_gamm_version() failure --- bin/gammu_get_unread_sms.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/bin/gammu_get_unread_sms.py b/bin/gammu_get_unread_sms.py index a8b0761..30e782f 100755 --- a/bin/gammu_get_unread_sms.py +++ b/bin/gammu_get_unread_sms.py @@ -4,7 +4,7 @@ # (C) 2003 - 2018 Michal Čihař - python-gammu # (C) 2015 - 2021 Raspian France - RaspianFrance/raspisms -# (C) 2022 Orsiris de Jong - NetInvent SASU +# (C) 2022 - Orsiris de Jong - NetInvent SASU from __future__ import print_function @@ -215,11 +215,14 @@ if __name__ == "__main__": # We need to check if we have gammu >= 1.42.0 since deleting sms with lower versions fail with: # Cannot delete sms: {'Text': 'The type of memory is not available or has been disabled.', 'Where': 'DeleteSMS', 'Code': 81} # see https://github.com/gammu/gammu/issues/460 - gammu_version = get_gammu_version() - if gammu_version[0] >= 1 and gammu_version[1] >= 42: - delete = True - else: - logger.warning("Cannot delete SMS. You need gammu >= 1.42.0.") - + try: + gammu_version = get_gammu_version() + if gammu_version[0] >= 1 and gammu_version[1] >= 42: + delete = True + else: + logger.warning("Cannot delete SMS. You need gammu >= 1.42.0.") + except TypeError: + logger.warning("Cannot get gammu version. SMS Deleting might not work properly.") + show_read = args.show_read main(config_file, delete, show_read) From cd5f6741640b16e53ba7c2f82e5652a30ec50799 Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Sun, 23 Oct 2022 11:21:11 +0200 Subject: [PATCH 3/4] Move comment to proper line --- bin/gammu_get_unread_sms.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bin/gammu_get_unread_sms.py b/bin/gammu_get_unread_sms.py index 30e782f..a3c4cbc 100755 --- a/bin/gammu_get_unread_sms.py +++ b/bin/gammu_get_unread_sms.py @@ -12,8 +12,8 @@ from __future__ import print_function __intname__ = "gammu_get_unread_sms.py" __author__ = "Orsiris de Jong - " -__version__ = "2.0.0" -__build__ = "2022102001" +__version__ = "2.0.1" +__build__ = "2022102301" __compat__ = "python2.7+" @@ -113,11 +113,10 @@ def load_sms_from_gammu(state_machine): remaining_sms = remaining_sms - len(sms) sms_list.append(sms) - # Concat multiSMS into list of sms that go together - except gammu.ERR_EMPTY: logger.debug("Finished reading all messages") + # Concat multiple SMS into list of sms that go together using LinkSMS return gammu.LinkSMS(sms_list) From 185d7772f70be981203c9e9b4b06bc6b0ef45a9b Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Wed, 26 Oct 2022 00:07:29 +0200 Subject: [PATCH 4/4] Improve logging --- bin/gammu_get_unread_sms.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/bin/gammu_get_unread_sms.py b/bin/gammu_get_unread_sms.py index a3c4cbc..26dcf72 100755 --- a/bin/gammu_get_unread_sms.py +++ b/bin/gammu_get_unread_sms.py @@ -12,8 +12,8 @@ from __future__ import print_function __intname__ = "gammu_get_unread_sms.py" __author__ = "Orsiris de Jong - " -__version__ = "2.0.1" -__build__ = "2022102301" +__version__ = "2.0.2" +__build__ = "2022102501" __compat__ = "python2.7+" @@ -33,6 +33,7 @@ logger = logging.getLogger(__name__) def get_logger(log_file): + # We would normally use ofunctions.logger_utils here with logger_get_logger(), but let's keep no dependencies try: try: filehandler = RotatingFileHandler( @@ -50,15 +51,21 @@ def get_logger(log_file): ) except OSError as exc: print("Cannot create log file: %s" % exc.__str__()) - return None + filehandler = None _logger = logging.getLogger() if _DEBUG: _logger.setLevel(logging.DEBUG) else: _logger.setLevel(logging.INFO) - _logger.addHandler(filehandler) - _logger.addHandler(logging.StreamHandler()) + + formatter = logging.Formatter("%(asctime)s :: %(levelname)s :: %(message)s") + if filehandler: + filehandler.setFormatter(formatter) + _logger.addHandler(filehandler) + consolehandler = logging.StreamHandler() + consolehandler.setFormatter(formatter) + _logger.addHandler(consolehandler) return _logger except Exception as exc: print("Cannot create logger instance: %s" % exc.__str__()) @@ -160,6 +167,8 @@ def render_sms_as_json(state_machine, sms_list, delete_sms, show_read_sms): def main(config_file, delete_sms, show_read): # type: (bool, bool) -> None + logger.debug("Running gammu receiver with config {}".format(config_file)) + try: # Mandatory modem config file # config_file = sys.argv[1] @@ -222,6 +231,6 @@ if __name__ == "__main__": logger.warning("Cannot delete SMS. You need gammu >= 1.42.0.") except TypeError: logger.warning("Cannot get gammu version. SMS Deleting might not work properly.") - + show_read = args.show_read main(config_file, delete, show_read)