[
  {
    "path": ".coveragerc",
    "content": "# .coveragerc to control coverage.py\n[run]\nbranch = True\nsource =\n    gsmmodem/\n    tools/gsmtermlib\nomit =\n    # Omit Python 2.6 and 3 compatibility wrappers\n    gsmmodem/compat.py\n    tools/gsmtermlib/posoptparse.py\n    # Omit GSMTerm UI\n    tools/gsmtermlib/terminal.py\n\n[report]\n# Regexes for lines to exclude from consideration\nexclude_lines =\n    # Have to re-enable the standard pragma\n    pragma: no cover\n    # Don't complain about Python version checks and subsequent monkey-patching\n    if sys.version_info\n    if PYTHON_VERSION\n\n"
  },
  {
    "path": ".flake8",
    "content": "[flake8]\nmax-line-length = 88\nextend-ignore =\n    E203  # \"Whitespace before ':'\" - not PEP-8 compliant\n    E501  # \"Line too long (82 >= 79 characters)\"\nper-file-ignores =\n    __init__.py:F401\n"
  },
  {
    "path": ".github/workflows/publish.yaml",
    "content": "# This workflow will upload a Python Package to PyPI when a release is published\n# For more information see:\n#   - https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/\n#   - https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries\n\nname: Upload Python Package\n\non:\n  push:\n    tags: [\"v*.*.*\"]\n  release:\n    types: [published]\n\njobs:\n  build:\n    name: Build package\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - name: Set up Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: \"3.x\"\n      - name: Install pypa/build\n        run: |\n          python -m pip install --upgrade pip\n          pip install --upgrade build\n      - name: Build package\n        run: |\n          python -m build\n      - name: Upload dist files\n        uses: actions/upload-artifact@v2\n        with:\n          name: dist-files\n          path: dist/\n          if-no-files-found: error\n  publish-test:\n    name: Publish to Test PyPI\n    if: github.event_name == 'push'\n    needs: [build]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Download dist files\n        uses: actions/download-artifact@v2\n        with:\n          name: dist-files\n          path: dist/\n      - name: Publish package to Test PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          repository_url: https://test.pypi.org/legacy/\n          user: __token__\n          password: ${{ secrets.TEST_PYPI_API_TOKEN }}\n  publish:\n    name: Publish to PyPI\n    if: github.event_name == 'release'\n    needs: [build]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Download dist files\n        uses: actions/download-artifact@v2\n        with:\n          name: dist-files\n          path: dist/\n      - name: Publish package to PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          user: __token__\n          password: ${{ secrets.PYPI_API_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n\n# Package-building stuff\n*.egg\n*.egg-info\ndist\nbuild\ndocs/_build\n\n# Eclipse project info\n.project\n.pydevproject\n.settings\n# PyCharm project info\n.idea\n\n# Symlinks (if present)\nexamples/gsmmodem\ntest/gsmmodem\ntest/gsmtermlib\ntools/gsmmodem\ntools/gsmtermlib/gsmmodem\ntools/init\n\n# Unit tests / coverage reports\n.coverage\nhtmlcov\n\n# Working copy files\n*.swp\n\n.DS_Store\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.1.0\n    hooks:\n      - id: check-case-conflict\n      - id: check-merge-conflict\n      - id: trailing-whitespace\n        args: [--markdown-linebreak-ext=md]\n      - id: end-of-file-fixer\n      - id: check-json\n      - id: check-toml\n      - id: check-yaml\n      - id: requirements-txt-fixer\n  - repo: https://github.com/pre-commit/mirrors-prettier\n    rev: v2.5.1\n    hooks:\n      - id: prettier\n  - repo: https://github.com/asottile/setup-cfg-fmt\n    rev: v1.20.0\n    hooks:\n      - id: setup-cfg-fmt\n  - repo: https://github.com/asottile/pyupgrade\n    rev: v2.31.0\n    hooks:\n      - id: pyupgrade\n        args: [--py38-plus]\n  ## <<< darker ##\n  ##  - repo: https://github.com/akaihola/darker\n  ##    rev: 1.4.0\n  ##    hooks:\n  ##    - id: darker\n  ##      args: [\"--isort\"] # TODO: Move to pyproject.toml\n  ##      additional_dependencies:\n  ##      - isort\n  ## === ##\n  - repo: https://github.com/PyCQA/isort\n    rev: 5.10.1\n    hooks:\n      - id: isort\n  - repo: https://github.com/psf/black\n    rev: 22.1.0\n    hooks:\n      - id: black # black-jupyter\n  ## >>> black and isort ##\n  - repo: https://github.com/PyCQA/bandit\n    rev: 1.7.2\n    hooks:\n      - id: bandit\n        args: [--recursive, --quiet]\n  - repo: https://gitlab.com/PyCQA/flake8\n    rev: 3.9.2\n    hooks:\n      - id: flake8 # E***, W***, F***\n        additional_dependencies:\n          - dlint # DUO***\n          - flake8-2020 # YTT***\n          - flake8-bugbear # B***\n          - flake8-builtins # A***\n          - flake8-comprehensions # C4**\n          - flake8-deprecated # D***\n          - flake8-variables-names # VNE***\n          - mccabe # C9**\n          - pep8-naming # N8**\n  # - repo: https://github.com/pre-commit/mirrors-mypy\n  #   rev: v0.790\n  #   hooks:\n  #     - id: mypy\n"
  },
  {
    "path": ".prettierrc.yaml",
    "content": "printWidth: 88\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\npython:\n  - \"3.6\"\n  - \"3.5\"\n  - \"3.4\"\n  - \"3.3\"\n  - \"2.7\"\ninstall:\n  # Install unittest2 on Python 2.6\n  - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi\n  # Install coveralls (for coveralls.io integration)\n  - pip install coveralls\n  - pip install -r requirements.txt\nscript: python setup.py coverage\nafter_success: coveralls\n"
  },
  {
    "path": "AUTHORS",
    "content": "Francois Aucamp <francois.aucamp@gmail.com>\n\nThanks to the following people for patches/suggestions:\ndavidphiliplee <https://github.com/davidphiliplee>\nchakphanu <https://github.com/chakphanu>\nJonathan Endersby <https://github.com/jonathanendersby>\nthe01 <https://github.com/the01>\nFrederico Rosmaninho\nDavid Beitey <https://github.com/davidjb>\nBOOMER74 <https://github.com/BOOMER74>\nCyril-Roques <https://github.com/Cyril-Roques>\nPeteLawler <https://github.com/PeteLawler>\nalex-eri <https://github.com/alex-eri>\ntomchy <https://github.com/tomchy>\nbennyslbs <https://github.com/bennyslbs>\nepol <https://github.com/epol>\nrags22489664 <https://github.com/rags22489664>\nfataevalex <https://github.com/fataevalex>\npaolo-losi <https://github.com/paolo-losi>\nyuriykashin <https://github.com/yuriykashin>\nfoXes68 <https://github.com/foXes68>\nbabca <https://github.com/babca>\n\n"
  },
  {
    "path": "COPYING",
    "content": "\t\t   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions. \n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version. \n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "ChangeLog",
    "content": "* Wed Mar 15 2017 babca - 0.12\n– stable release\n- unit tests fixed after rapid merging – credits to: tomchy\n- python3.6 support added\n– message concatenation fixes and more\n\n* Thu Nov 10 2016 babca - 0.11\n- added getter for SIM own number\n- added option for blocking incoming calls (GSMBUSY)\n- various python3 fixes\n\n* Thu Aug 18 2016 babca - 0.10\n– Probably a new code maintainer for 2016\n- All commits published for the last 3 years merged into a one branch\n– Compatibilty for python3 added, needs further testing!\n– experimental GPRS support\n– more:\n    – change AT_CNMI command if needed\n    – waitingForModemToStartInSeconds\n    – timeouts increased\n    – ability to check SMS encodings supported by modem - smsSupportedEncoding()\n    – better modem specific support (incl. simcom)\n    – TE SMS status reports handling support\n    – option to disable requesting delivery reports\n    – incoming DTMF support\n– todo: check AT+CMGD support for 1 or 2 params and use appropriate command format\n\n* Thu Jul 18 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.9\n- Added UDH support for SMS PDUs\n- Stored messages APIs made public\n- USSD support improved on different modem types\n- Vastly improved unit test coverage\n- Lots of bugfixes and stability improvements\n\n* Tue May 21 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.8\n- Support added for ZTE modems\n- Improved support for Huawei modems\n- Outgoing call status can now be tracked via polling (for unknown modems)\n- SMSC property added\n- Fixes for SMS sending and receiving on different modems\n- Added callback mechanism for outoging call status updates\n\n* Fri Apr 19 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.7\n- Support added for tracking SMS status reports\n- PIN unlock support\n- SMS API cleaned up\n- Bugfixes\n\n* Tue Apr 03 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.6\n- Added support for PDU mode SMS\n- Default SMS read/write mode is now PDU mode\n- Added identify-modem.py script to assist with debugging different modem types\n- Lots of bugfixes\n- Lots of tests added\n\n* Wed Mar 06 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.5\n- Many bugfixes and improvements, especially to USSD handling\n- Improved exceptions to allow more Pythonic error handling\n- Tests added for SMS API\n- Unit tests speeded up\n\n* Tue Mar 05 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.4\n- Support added for making voice calls\n- Library and utilities now supported under Python 2.6\n- Support added for Wavecom modems\n- Tests expanded\n\n* Tue Feb 26 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.3\n- USSD functionality added\n- GsmModem class now exposed in main gsmmodem package\n- GsmModem test cases added for USSD functionality\n- Some fixes to GSMTerm tests\n\n* Mon Feb 18 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.2\n- Renamed \"gsmterm\" module to \"gsmtermlib\" to avoid conflict between startup\nscript and module\n\n* Wed Feb 13 2013 Francois Aucamp <francois.aucamp@gmail.com> - 0.1\n- Initial 0.1 release to github\n- GsmModem class functionality more-or-less where I need it: handles incoming\nSMS messages and phone calls, can send SMS messages\n- GSMTerm essentially finished\n- SendSMS user script needs some polish\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include AUTHORS\ninclude ChangeLog\ninclude COPYING\ninclude requirements.txt\ninclude examples/*.py\n"
  },
  {
    "path": "README.rst",
    "content": "python-gsmmodem-new\n===================\n*GSM modem module for Python*\n\npython-gsmmodem is a module that allows easy control of a GSM modem attached\nto the system. It also includes a couple of useful commandline utilities for\ninteracting with a GSM modem. \n\nIts features include:\n\n- simple methods for sending SMS messages, checking signal level, etc\n- easy-to-use API for starting and responding to USSD sessions and making voice\n  calls\n- handling incoming phone calls and received SMS messages via callback methods\n- support for SMS PDU and text mode\n- support for tracking SMS status reports\n- wraps AT command errors into Python exceptions by default\n- modular design; you easily issue your own AT commands to the modem (with\n  error  checking), or read/write directly from/to the modem if you prefer\n- comprehensive test suite\n\nBundled utilities:\n\n- **GSMTerm**: an easy-to-use serial terminal for communicating with an\n  attached GSM modem. It features command completion, built-in help for many AT\n  commands, history, context-aware prompt, etc.\n- **sendsms.py**: a simple command line script to send SMS messages\n- **identify-modem.py**: simple utility to identify attached modem. Can also be\n  used to provide debug information used for development of python-gsmmodem. \n\nHow to use this package\n-----------------------\n\nGo to `examples/` directory in this repo.\n\n\nRequirements\n------------\n\n- Python 3.3 or later\n- pySerial\n\n\nHow to install this package\n---------------------------\n\nThere are multiple ways to install ``python-gsmmodem-new`` package:\n\nAutomatic installation of the latest \"stable\" release from PyPI\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n    pip install python-gsmmodem-new\n\n`pip <http://www.pip-installer.org>`_ will automatically download and install\nall dependencies, as required. You can also utilise ``easy_install`` in the\nsame manner as using ``pip`` above.  \n\nIf you are utilising ``python-gsmmodem-new`` as part of another project,\nadd it to your ``install_requires`` section of your ``setup.py`` file and\nupon your project's installation, it will be pulled in automatically.\n\nManual installation of the latest \"stable\" release from PyPI\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nDownload a ``python-gsmmodem-new`` archive from `PyPI\n<https://pypi.python.org/pypi/python-gsmmodem-new>`_, extract it and install the package with command::\n\n    python setup.py install\n\nNote that ``python-gsmmodem-new`` package relies on ``pySerial`` for serial communications: \nhttps://github.com/pyserial/pyserial\n\nInstallation of the latest commit from GitHub\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nClone from GitHub::\n\n    git clone https://github.com/babca/python-gsmmodem.git\n    cd python-gsmmodem/\n    python setup.py install\n\nNote that ``python-gsmmodem-new`` package relies on ``pySerial`` for serial communications: \nhttps://github.com/pyserial/pyserial\n\nTesting the package\n-------------------\n\n.. |Build Status| image::  https://travis-ci.org/babca/python-gsmmodem.svg?branch=master\n.. _Build Status: https://travis-ci.org/babca/python-gsmmodem\n\n.. |Coverage Status| image:: https://coveralls.io/repos/github/babca/python-gsmmodem/badge.svg?branch=master\n.. _Coverage Status: https://coveralls.io/github/babca/python-gsmmodem?branch=master\n\n|Build Status|_ |Coverage Status|_\n\nTo run all unit tests, do::\n\n    python setup.py test\n\nUnit test code coverage information may be generated by using `coverage\n<https://pypi.python.org/pypi/coverage/>`_. You can execute it directly from\nsetup.py by doing::\n\n    python setup.py coverage\n\nThis will run all unit tests and report on code coverage statistics.\n\n\nBuilding documentation\n----------------------\n\nThis package contains `Sphinx <http://sphinx-doc.org>`_-based documentation.\nTo manually build or test the documentation locally, do the following::\n\n   git clone https://github.com/babca/python-gsmmodem.git\n   cd python-gsmmodem\n   pip install .[doc]\n   cd doc\n   make html\n\nFor true isolation, you may wish to run the above commands within a\n`virtualenv <http://www.virtualenv.org/>`_, which will help you manage\nthis development installation.\n\n\nLicense information\n-------------------\n\nCopyright (C) 2013 Francois Aucamp  \nSee AUTHORS for all authors and contact information. \n\nLicense: GNU Lesser General Public License, version 3 or later; see COPYING\n         included in this archive for details.\n\nFAQ\n---\n\nList all modem ports\n~~~~~~~~~~~~~~~~~~~~\n\nYou can simply list all ttyUSB devices before and after pluging the modem in.\n\n  ls /dev/ttyUSB*\n\n\nDevice or resource busy error\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCheck running processes. The device could be occupied by another program or another instance of gsmmodem which is still running in the background. Run ``sudo lsof | grep tty``, try to locate the problematic process and ``sudo kill <PID>``.\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/python-gsmmodem.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/python-gsmmodem.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/python-gsmmodem\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-gsmmodem\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/api.rst",
    "content": "API\n===\n\n\nGSM Modem\n---------\n\n.. automodule:: gsmmodem.modem\n   :members:\n\n\nSerial Communications\n---------------------\n\n.. automodule:: gsmmodem.serial_comms\n   :members:\n\n\nPDU\n---\n\n.. automodule:: gsmmodem.pdu\n   :members:\n\n\nUtilities\n---------\n\n.. automodule:: gsmmodem.util\n   :members:\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# python-gsmmodem documentation build configuration file, created by\n# sphinx-quickstart on Sun Aug 11 20:50:25 2013.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys, os\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.viewcode']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'python-gsmmodem'\ncopyright = u'2013, Developers'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '0.9'\n# The full version, including alpha/beta/rc tags.\nrelease = '0.9'\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'default'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'python-gsmmodemdoc'\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n  ('index', 'python-gsmmodem.tex', u'python-gsmmodem Documentation',\n   u'Developers', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'python-gsmmodem', u'python-gsmmodem Documentation',\n     [u'Developers'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'python-gsmmodem', u'python-gsmmodem Documentation',\n   u'Developers', 'python-gsmmodem', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n"
  },
  {
    "path": "docs/examples.rst",
    "content": "Examples\n========\n\n\nDial Callback\n-------------\n\n.. literalinclude:: ../examples/dial_callback_demo.py\n   :language: python\n\n\nDial Polling\n------------\n\n.. literalinclude:: ../examples/dial_polling_demo.py\n   :language: python\n\n\nIncoming Call Handling\n----------------------\n\n.. literalinclude:: ../examples/incoming_call_demo.py\n   :language: python\n\n\nSMS Handling\n------------\n\n.. literalinclude:: ../examples/sms_handler_demo.py\n   :language: python\n\n\nUSSD Sessions\n-------------\n\n.. literalinclude:: ../examples/ussd_demo.py\n   :language: python\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. python-gsmmodem documentation master file, created by\n   sphinx-quickstart on Sun Aug 11 20:50:25 2013.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\nWelcome to python-gsmmodem's documentation!\n===========================================\n\n\n.. include:: ../README.rst\n\n.. automodule:: gsmmodem\n\n\nExamples and API\n================\n\n.. toctree::\n   :maxdepth: 3\n\n   examples.rst\n   api.rst\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\nset BUILDDIR=_build\r\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\r\nset I18NSPHINXOPTS=%SPHINXOPTS% .\r\nif NOT \"%PAPER%\" == \"\" (\r\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\r\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\r\n)\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\nif \"%1\" == \"help\" (\r\n\t:help\r\n\techo.Please use `make ^<target^>` where ^<target^> is one of\r\n\techo.  html       to make standalone HTML files\r\n\techo.  dirhtml    to make HTML files named index.html in directories\r\n\techo.  singlehtml to make a single large HTML file\r\n\techo.  pickle     to make pickle files\r\n\techo.  json       to make JSON files\r\n\techo.  htmlhelp   to make HTML files and a HTML help project\r\n\techo.  qthelp     to make HTML files and a qthelp project\r\n\techo.  devhelp    to make HTML files and a Devhelp project\r\n\techo.  epub       to make an epub\r\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\r\n\techo.  text       to make text files\r\n\techo.  man        to make manual pages\r\n\techo.  texinfo    to make Texinfo files\r\n\techo.  gettext    to make PO message catalogs\r\n\techo.  changes    to make an overview over all changed/added/deprecated items\r\n\techo.  xml        to make Docutils-native XML files\r\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\r\n\techo.  linkcheck  to check all external links for integrity\r\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"clean\" (\r\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\r\n\tdel /q /s %BUILDDIR%\\*\r\n\tgoto end\r\n)\r\n\r\n\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.http://sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\nif \"%1\" == \"html\" (\r\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"dirhtml\" (\r\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"singlehtml\" (\r\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pickle\" (\r\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the pickle files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"json\" (\r\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the JSON files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"htmlhelp\" (\r\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run HTML Help Workshop with the ^\r\n.hhp project file in %BUILDDIR%/htmlhelp.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"qthelp\" (\r\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\r\n.qhcp project file in %BUILDDIR%/qthelp, like this:\r\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\python-gsmmodem.qhcp\r\n\techo.To view the help file:\r\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\python-gsmmodem.ghc\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"devhelp\" (\r\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"epub\" (\r\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latex\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdf\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf\r\n\tcd %BUILDDIR%/..\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdfja\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf-ja\r\n\tcd %BUILDDIR%/..\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"text\" (\r\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The text files are in %BUILDDIR%/text.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"man\" (\r\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"texinfo\" (\r\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"gettext\" (\r\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"changes\" (\r\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.The overview file is in %BUILDDIR%/changes.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"linkcheck\" (\r\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Link check complete; look for any errors in the above output ^\r\nor in %BUILDDIR%/linkcheck/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"doctest\" (\r\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of doctests in the sources finished, look at the ^\r\nresults in %BUILDDIR%/doctest/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"xml\" (\r\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pseudoxml\" (\r\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\r\n\tgoto end\r\n)\r\n\r\n:end\r\n"
  },
  {
    "path": "examples/dial_callback_demo.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nDemo: dial a number (using callbacks to track call status)\n\nSimple demo app that makes a voice call and plays sone DTMF tones (if supported by modem)\nwhen the call is answered, and hangs up the call.\nIt uses the dial() methods callback mechanism to be informed when the call is answered and ended.\n\nNote: you need to modify the NUMBER variable for this to work\n\"\"\"\n\nfrom __future__ import print_function\n\nimport sys, time, logging\n\nPORT = '/dev/ttyUSB2'\nBAUDRATE = 115200\nNUMBER = '00000' # Number to dial - CHANGE THIS TO A REAL NUMBER\nPIN = None # SIM card PIN (if any)\n\nfrom gsmmodem.modem import GsmModem\nfrom gsmmodem.exceptions import InterruptedException, CommandError\n\nwaitForCallback = True\n\ndef callStatusCallback(call):\n    global waitForCallback\n    print('Call status update callback function called')\n    if call.answered:\n        print('Call has been answered; waiting a while...')\n        # Wait for a bit - some older modems struggle to send DTMF tone immediately after answering a call\n        time.sleep(3.0)\n        print('Playing DTMF tones...')\n        try:\n            if call.active: # Call could have been ended by remote party while we waited in the time.sleep() call\n                call.sendDtmfTone('9515999955951')\n        except InterruptedException as e:\n            # Call was ended during playback\n            print('DTMF playback interrupted: {0} ({1} Error {2})'.format(e, e.cause.type, e.cause.code))\n        except CommandError as e:\n            print('DTMF playback failed: {0}'.format(e))\n        finally:\n            if call.active: # Call is still active\n                print('Hanging up call...')\n                call.hangup()\n            waitForCallback = False\n    else:\n        # Call is no longer active (remote party ended it)\n        print('Call has been ended by remote party')\n        waitForCallback = False\n\ndef main():\n    if NUMBER == None or NUMBER == '00000':\n        print('Error: Please change the NUMBER variable\\'s value before running this example.')\n        sys.exit(1)\n    print('Initializing modem...')\n    logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    modem = GsmModem(PORT, BAUDRATE)\n    modem.connect(PIN)\n    print('Waiting for network coverage...')\n    modem.waitForNetworkCoverage(30)\n    print('Dialing number: {0}'.format(NUMBER))\n    call = modem.dial(NUMBER, callStatusUpdateCallbackFunc=callStatusCallback)\n    global waitForCallback\n    while waitForCallback:\n        time.sleep(0.1)\n    print('Done')\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "examples/dial_polling_demo.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nDemo: dial a number (simple example using polling to check call status)\n\nSimple demo app that makes a voice call and plays sone DTMF tones (if supported by modem)\nwhen the call is answered, and hangs up the call.\nIt polls the call status to see if the call has been answered\n\nNote: you need to modify the NUMBER variable for this to work\n\"\"\"\n\nfrom __future__ import print_function\n\nimport sys, time, logging\n\nPORT = '/dev/ttyUSB2'\nBAUDRATE = 115200\nNUMBER = '00000' # Number to dial - CHANGE THIS TO A REAL NUMBER\nPIN = None # SIM card PIN (if any)\n\nfrom gsmmodem.modem import GsmModem\nfrom gsmmodem.exceptions import InterruptedException, CommandError\n\ndef main():\n    if NUMBER == None or NUMBER == '00000':\n        print('Error: Please change the NUMBER variable\\'s value before running this example.')\n        sys.exit(1)\n    print('Initializing modem...')\n    #logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    modem = GsmModem(PORT, BAUDRATE)\n    modem.connect(PIN)\n    print('Waiting for network coverage...')\n    modem.waitForNetworkCoverage(30)\n    print('Dialing number: {0}'.format(NUMBER))\n    call = modem.dial(NUMBER)\n    print('Waiting for call to be answered/rejected')\n    wasAnswered = False\n    while call.active:\n        if call.answered:\n            wasAnswered = True\n            print('Call has been answered; waiting a while...')\n            # Wait for a bit - some older modems struggle to send DTMF tone immediately after answering a call\n            time.sleep(3.0)\n            print('Playing DTMF tones...')\n            try:\n                if call.active: # Call could have been ended by remote party while we waited in the time.sleep() call\n                    call.sendDtmfTone('9515999955951')\n            except InterruptedException as e:\n                # Call was ended during playback\n                print('DTMF playback interrupted: {0} ({1} Error {2})'.format(e, e.cause.type, e.cause.code))\n            except CommandError as e:\n                print('DTMF playback failed: {0}'.format(e))\n            finally:\n                if call.active: # Call is still active\n                    print('Hanging up call...')\n                    call.hangup()\n                else: # Call is no longer active (remote party ended it)\n                    print('Call has been ended by remote party')\n        else:\n            # Wait a bit and check again\n            time.sleep(0.5)\n    if not wasAnswered:\n        print('Call was not answered by remote party')\n    print('Done.')\n    modem.close()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "examples/incoming_call_demo.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nDemo: handle incoming calls\n\nSimple demo app that listens for incoming calls, displays the caller ID,\noptionally answers the call and plays sone DTMF tones (if supported by modem),\nand hangs up the call.\n\"\"\"\n\nfrom __future__ import print_function\n\nimport time, logging\n\nPORT = '/dev/ttyUSB2'\nBAUDRATE = 115200\nPIN = None # SIM card PIN (if any)\n\nfrom gsmmodem.modem import GsmModem\nfrom gsmmodem.exceptions import InterruptedException\n\ndef handleIncomingCall(call):\n    if call.ringCount == 1:\n        print('Incoming call from:', call.number)\n    elif call.ringCount >= 2:\n        if call.dtmfSupport:\n            print('Answering call and playing some DTMF tones...')\n            call.answer()\n            # Wait for a bit - some older modems struggle to send DTMF tone immediately after answering a call\n            time.sleep(2.0)\n            try:\n                call.sendDtmfTone('9515999955951')\n            except InterruptedException as e:\n                # Call was ended during playback\n                print('DTMF playback interrupted: {0} ({1} Error {2})'.format(e, e.cause.type, e.cause.code))\n            finally:\n                if call.answered:\n                    print('Hanging up call.')\n                    call.hangup()\n        else:\n            print('Modem has no DTMF support - hanging up call.')\n            call.hangup()\n    else:\n        print(' Call from {0} is still ringing...'.format(call.number))\n\ndef main():\n    print('Initializing modem...')\n    #logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    modem = GsmModem(PORT, BAUDRATE, incomingCallCallbackFunc=handleIncomingCall)\n    modem.connect(PIN)\n    print('Waiting for incoming calls...')\n    try:\n        modem.rxThread.join(2**31) # Specify a (huge) timeout so that it essentially blocks indefinitely, but still receives CTRL+C interrupt signal\n    finally:\n        modem.close()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "examples/own_number_demo.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nDemo: read own phone number\n\"\"\"\n\nfrom __future__ import print_function\n\nimport logging\n\nPORT = '/dev/vmodem0'\nBAUDRATE = 115200\nPIN = None # SIM card PIN (if any)\n\nfrom gsmmodem.modem import GsmModem\n\ndef main():\n    print('Initializing modem...')\n    # Uncomment the following line to see what the modem is doing:\n    logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    modem = GsmModem(PORT, BAUDRATE)\n    modem.connect(PIN)\n\n    number = modem.ownNumber\n    print(\"The SIM card phone number is:\")\n    print(number)\n\n    # Uncomment the following block to change your own number.\n    # modem.ownNumber = \"+000123456789\" # lease empty for removing the phone entry altogether\n\n    # number = modem.ownNumber\n    # print(\"A new phone number is:\")\n    # print(number)\n\n    # modem.close();\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "examples/send_sms_demo.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\nDemo: Send Simple SMS Demo\n\nSimple demo to send sms via gsmmodem package\n\"\"\"\nfrom __future__ import print_function\n\nimport logging\n\nfrom gsmmodem.modem import GsmModem, SentSms\n\n# PORT = 'COM5' # ON WINDOWS, Port is from COM1 to COM9 ,\n# We can check using the 'mode' command in cmd\nPORT = '/dev/ttyUSB2'\nBAUDRATE = 115200\nSMS_TEXT = 'A good teacher is like a candle, it consumes itself to light the way for others.'\nSMS_DESTINATION = 'YOUR PHONE NUMBER HERE'\nPIN = None  # SIM card PIN (if any)\n\n\ndef main():\n    print('Initializing modem...')\n    modem = GsmModem(PORT, BAUDRATE)\n    modem.connect(PIN)\n    modem.waitForNetworkCoverage(10)\n    print('Sending SMS to: {0}'.format(SMS_DESTINATION))\n\n    response = modem.sendSms(SMS_DESTINATION, SMS_TEXT, True)\n    if type(response) == SentSms:\n        print('SMS Delivered.')\n    else:\n        print('SMS Could not be sent')\n\n    modem.close()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "examples/sms_handler_demo.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nDemo: handle incoming SMS messages by replying to them\n\nSimple demo app that listens for incoming SMS messages, displays the sender's number\nand the messages, then replies to the SMS by saying \"thank you\"\n\"\"\"\n\nfrom __future__ import print_function\n\nimport logging\n\nPORT = '/dev/ttyUSB2'\nBAUDRATE = 115200\nPIN = None # SIM card PIN (if any)\n\nfrom gsmmodem.modem import GsmModem\n\ndef handleSms(sms):\n    print(u'== SMS message received ==\\nFrom: {0}\\nTime: {1}\\nMessage:\\n{2}\\n'.format(sms.number, sms.time, sms.text))\n    print('Replying to SMS...')\n    sms.reply(u'SMS received: \"{0}{1}\"'.format(sms.text[:20], '...' if len(sms.text) > 20 else ''))\n    print('SMS sent.\\n')\n\ndef main():\n    print('Initializing modem...')\n    # Uncomment the following line to see what the modem is doing:\n    logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    modem = GsmModem(PORT, BAUDRATE, smsReceivedCallbackFunc=handleSms)\n    modem.smsTextMode = False\n    modem.connect(PIN)\n    print('Waiting for SMS message...')\n    try:\n        modem.rxThread.join(2**31) # Specify a (huge) timeout so that it essentially blocks indefinitely, but still receives CTRL+C interrupt signal\n    finally:\n        modem.close()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "examples/ussd_demo.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nDemo: Simple USSD example\n\nSimple demo app that initiates a USSD session, reads the string response and closes the session\n(if it wasn't closed by the network)\n\nNote: for this to work, a valid USSD string for your network must be used.\n\"\"\"\n\nfrom __future__ import print_function\n\nimport logging\n\nPORT = '/dev/ttyUSB2'\nBAUDRATE = 115200\nUSSD_STRING = '*101#'\nPIN = None # SIM card PIN (if any)\n\nfrom gsmmodem.modem import GsmModem\n\ndef main():\n    print('Initializing modem...')\n    #logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    modem = GsmModem(PORT, BAUDRATE)\n    modem.connect(PIN)\n    modem.waitForNetworkCoverage(10)\n    print('Sending USSD string: {0}'.format(USSD_STRING))\n    response = modem.sendUssd(USSD_STRING) # response type: gsmmodem.modem.Ussd\n    print('USSD reply received: {0}'.format(response.message))\n    if response.sessionActive:\n        print('Closing USSD session.')\n        # At this point, you could also reply to the USSD message by using response.reply()\n        response.cancel()\n    else:\n        print('USSD session was ended by network.')\n    modem.close()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "gsmmodem/__init__.py",
    "content": "\"\"\" Package that allows easy control of an attached GSM modem \n\nThe main class for controlling a modem is GsmModem, which can be imported\ndirectly from this module.\n\nOther important and useful classes are:\ngsmmodem.modem.IncomingCall: wraps an incoming call and passed to the incoming call hanndler callback function\ngsmmodem.modem.ReceivedSms: wraps a received SMS message and passed to the sms received hanndler callback function\ngsmmodem.modem.SentSms: returned when sending SMS messages; used for tracking the status of the SMS message\n\nAll python-gsmmodem-specific exceptions are defined in the gsmmodem.modem.exceptions package.\n\n@author: Francois Aucamp <francois.aucamp@gmail.com>\n@license: LGPLv3+\n\"\"\"\n\nfrom .modem import GsmModem\n"
  },
  {
    "path": "gsmmodem/compat.py",
    "content": "\"\"\" Contains monkey-patched equivalents for a few commonly-used Python 2.7-and-higher functions.\nUsed to provide backwards-compatibility with Python 2.6\n\"\"\"\nimport sys\nif sys.version_info[0] == 2 and sys.version_info[1] < 7:\n    import threading\n    \n    # threading.Event.wait() always returns None in Python < 2.7 so we need to patch it\n    if hasattr(threading, '_Event'): # threading.Event is a function that return threading._Event\n        # This is heavily Python-implementation-specific, so patch where we can, otherwise leave it\n        def wrapWait(func):\n            def newWait(self, timeout=None):\n                func(self, timeout)\n                return self.is_set()            \n            return newWait\n        threading._Event.wait = wrapWait(threading._Event.wait)\n    else:\n        raise ImportError('Could not patch this version of Python 2.{0} for compatibility with python-gsmmodem.'.format(sys.version_info[1]))\nif sys.version_info[0] == 2:\n    str = str\nelse:\n    str = lambda x: x"
  },
  {
    "path": "gsmmodem/exceptions.py",
    "content": "\"\"\" Module defines exceptions used by gsmmodem \"\"\"\n\nclass GsmModemException(Exception):\n    \"\"\" Base exception raised for error conditions when interacting with the GSM modem \"\"\"\n\n\nclass TimeoutException(GsmModemException):\n    \"\"\" Raised when a write command times out \"\"\"\n    \n    def __init__(self, data=None):\n        \"\"\" @param data: Any data that was read was read before timeout occurred (if applicable) \"\"\"\n        super(TimeoutException, self).__init__(data)\n        self.data = data \n\n\nclass InvalidStateException(GsmModemException):\n    \"\"\" Raised when an API method call is invoked on an object that is in an incorrect state \"\"\"\n\n\nclass InterruptedException(InvalidStateException):\n    \"\"\" Raised when execution of an AT command is interrupt by a state change.\n    May contain another exception that was the cause of the interruption \"\"\"\n    \n    def __init__(self, message, cause=None):\n        \"\"\" @param cause: the exception that caused this interruption (usually a CmeError) \"\"\"\n        super(InterruptedException, self).__init__(message)\n        self.cause = cause\n\n\nclass CommandError(GsmModemException):\n    \"\"\" Raised if the modem returns an error in response to an AT command\n     \n    May optionally include an error type (CME or CMS) and -code (error-specific).\n    \"\"\"\n    \n    _description = ''\n    \n    def __init__(self, command=None, type=None, code=None):\n        self.command = command\n        self.type = type\n        self.code = code\n        if type != None and code != None:\n            super(CommandError, self).__init__('{0} {1}{2}'.format(type, code, ' ({0})'.format(self._description) if len(self._description) > 0 else ''))\n        elif command != None:\n            super(CommandError, self).__init__(command)\n        else:\n            super(CommandError, self).__init__()\n\n\nclass CmeError(CommandError):\n    \"\"\" ME error result code : +CME ERROR: <error>\n     \n    Issued in response to an AT command\n    \"\"\"\n\n    def __new__(cls, *args, **kwargs):\n        # Return a specialized version of this class if possible\n        if len(args) >= 2:\n            code = args[1]\n            if code == 11:\n                return PinRequiredError(args[0])\n            elif code == 16:\n                return IncorrectPinError(args[0])\n            elif code == 12:\n                return PukRequiredError(args[0])\n        return super(CmeError, cls).__new__(cls, *args, **kwargs)\n\n    def __init__(self, command, code):\n        super(CmeError, self).__init__(command, 'CME', code)\n\n\nclass SecurityException(CmeError):\n    \"\"\" Security-related CME error \"\"\"\n\n    def __init__(self, command, code):\n        super(SecurityException, self).__init__(command, code)\n\n\nclass PinRequiredError(SecurityException):\n    \"\"\" Raised if an operation failed because the SIM card's PIN has not been entered \"\"\"\n\n    _description = 'SIM card PIN is required'\n\n    def __init__(self, command, code=11):\n        super(PinRequiredError, self).__init__(command, code)\n\n\nclass IncorrectPinError(SecurityException):\n    \"\"\" Raised if an incorrect PIN is entered \"\"\"\n\n    _description = 'Incorrect PIN entered'\n\n    def __init__(self, command, code=16):\n        super(IncorrectPinError, self).__init__(command, code)\n\n\nclass PukRequiredError(SecurityException):\n    \"\"\" Raised an operation failed because the SIM card's PUK is required (SIM locked) \"\"\"\n    \n    _description = \"PUK required (SIM locked)\"\n    \n    def __init__(self, command, code=12):\n        super(PukRequiredError, self).__init__(command, code)\n\n\nclass CmsError(CommandError):\n    \"\"\" Message service failure result code: +CMS ERROR : <er>\n    \n    Issued in response to an AT command\n    \"\"\"\n\n    def __new__(cls, *args, **kwargs):\n        # Return a specialized version of this class if possible\n        if len(args) >= 2:\n            code = args[1]\n            if code == 330:\n                return SmscNumberUnknownError(args[0])\n        return super(CmsError, cls).__new__(cls, *args, **kwargs)\n    \n    def __init__(self, command, code):\n        super(CmsError, self).__init__(command, 'CMS', code)\n\n\nclass SmscNumberUnknownError(CmsError):\n    \"\"\" Raised if the SMSC (service centre) address is missing when trying to send an SMS message \"\"\"\n    \n    _description = 'SMSC number not set'\n\n    def __init__(self, command, code=330):\n        super(SmscNumberUnknownError, self).__init__(command, code)\n\n\nclass EncodingError(GsmModemException):\n    \"\"\" Raised if a decoding- or encoding operation failed \"\"\"\n"
  },
  {
    "path": "gsmmodem/gprs.py",
    "content": "# -*- coding: utf8 -*-\n\n\"\"\" GPRS/Data-specific classes \n\nBRANCH: mms\n\nPLEASE NOTE: *Everything* in this file (PdpContext, GprsModem class, etc) is experimental.\nThis is NOT meant to be used in production in any way; the API is completely unstable,\nno unit tests will be written for this in the forseeable future, and stuff may generally\nbreak and cause riots. Please do not file bug reports against this branch unless you\nhave a patch to go along with it, but even then: remember that this entire \"mms\" branch\nis exploratory; I simply want to see what the possibilities are with it.\n\nUse the \"main\" branch, and the GsmModem class if you want to build normal applications.\n\"\"\"\n\nimport re\n\nfrom .util import allLinesMatchingPattern\nfrom .modem import GsmModem\n\nclass PdpContext(object):\n    \"\"\" Packet Data Protocol (PDP) context parameter values \"\"\"\n    def __init__(self, cid, pdpType, apn, pdpAddress=None, dataCompression=0, headerCompression=0):\n        \"\"\" Construct a new Packet Data Protocol context\n        \n        @param cid: PDP Context Identifier - specifies a particular PDP context definition\n        @type cid: int\n        @param pdpType: the type of packet data protocol (IP, PPP, IPV6, etc)\n        @type pdpType: str\n        @param apn: Access Point Name; logical name used to select the GGSN or external packet data network\n        @type apn: str\n        @param pdpAddress: identifies the MT in the address space applicable to the PDP. If None, a dynamic address may be requested.\n        @type pdpAddress: str\n        @param dataCompression: PDP data compression; 0 == off, 1 == on\n        @type dataCompression: int\n        @param headerCompression: PDP header compression; 0 == off, 1 == on\n        @type headerCompression: int\n        \"\"\"\n        self.cid = cid\n        self.pdpType = pdpType\n        self.apn = apn\n        self.pdpAddress = pdpAddress\n        self.dataCompression = dataCompression\n        self.headerCompression = headerCompression\n\n\nclass GprsModem(GsmModem):\n    \"\"\" EXPERIMENTAL: Specialized version of GsmModem that includes GPRS/data-specific commands \"\"\"\n    \n    @property\n    def pdpContexts(self):\n        \"\"\" Currently-defined Packet Data Protocol (PDP) context list\n        \n        PDP paramter values returned include PDP type (IP, IPV6, PPP, X.25 etc), APN, \n        data compression, header compression, etc.\n        \n        @return: a list of currently-defined PDP contexts\n        \"\"\"\n        result = []\n        cgdContResult = self.write('AT+CGDCONT?')\n        matches = allLinesMatchingPattern(re.compile(r'^\\+CGDCONT:\\s*(\\d+),\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",(\\d+),(\\d+)'), cgdContResult)\n        for cgdContMatch in matches:\n            cid, pdpType, apn, pdpAddress, dataCompression, headerCompression = cgdContMatch.groups()\n            pdpContext = PdpContext(cid, pdpType, apn, pdpAddress, dataCompression, headerCompression)\n            result.append(pdpContext)\n        return result\n    \n    @property\n    def defaultPdpContext(self):\n        \"\"\" @return: the default PDP context, or None if not defined \"\"\"\n        pdpContexts = self.pdpContexts\n        return pdpContexts[0] if len(pdpContexts) > 0 else None\n    @defaultPdpContext.setter\n    def defaultPdpContext(self, pdpContext):\n        \"\"\" Set the default PDP context (or clear it by setting it to None) \"\"\"\n        self.write('AT+CGDCONT=,\"{0}\",\"{1}\",\"{2}\",{3},{4}'.format(pdpContext.pdpType, pdpContext.apn, pdpContext.pdpAddress or '', pdpContext.dataCompression, pdpContext.headerCompression))\n    \n    def definePdpContext(self, pdpContext):\n        \"\"\" Define a new Packet Data Protocol context, or overwrite an existing one\n        \n        @param pdpContext: The PDP context to define\n        @type pdpContext: gsmmodem.gprs.PdpContext\n        \"\"\"\n        self.write('AT+CGDCONT={0},\"{1}\",\"{2}\",\"{3}\",{4},{5}'.format(pdpContext.cid or '', pdpContext.pdpType, pdpContext.apn, pdpContext.pdpAddress or '', pdpContext.dataCompression, pdpContext.headerCompression))\n\n    def initDataConnection(self, pdpCid=1):\n        \"\"\" Initializes a packet data (GPRS) connection using the specified PDP Context ID \"\"\"\n        # From this point on, we don't want the read thread interfering\n        #self.log.debug('Stopping read thread')\n        #self.alive = False\n        #self.rxThread.join()\n        self.log.debug('Init data connection')\n        self.write('ATD*99#', expectedResponseTermSeq=\"CONNECT\\r\")\n        self.log.debug('Data connection open; ready for PPP comms')\n        # From here on we use PPP to communicate with the network\n"
  },
  {
    "path": "gsmmodem/modem.py",
    "content": "#!/usr/bin/env python\n\n\"\"\" High-level API classes for an attached GSM modem \"\"\"\n\nimport sys, re, logging, weakref, time, threading, abc, codecs\nfrom datetime import datetime\nfrom time import sleep\n\nfrom .serial_comms import SerialComms\nfrom .exceptions import CommandError, InvalidStateException, CmeError, CmsError, InterruptedException, TimeoutException, PinRequiredError, IncorrectPinError, SmscNumberUnknownError\nfrom .pdu import encodeSmsSubmitPdu, decodeSmsPdu, encodeGsm7, encodeTextMode\nfrom .util import SimpleOffsetTzInfo, lineStartingWith, allLinesMatchingPattern, parseTextModeTimeStr, removeAtPrefix\n\n#from . import compat # For Python 2.6 compatibility\nfrom gsmmodem.util import lineMatching\nfrom gsmmodem.exceptions import EncodingError\nPYTHON_VERSION = sys.version_info[0]\n\nCTRLZ = '\\x1a'\nTERMINATOR = '\\r'\n\nif PYTHON_VERSION >= 3:\n    xrange = range\n    dictValuesIter = dict.values\n    dictItemsIter = dict.items\nelse: #pragma: no cover\n    dictValuesIter = dict.itervalues\n    dictItemsIter = dict.iteritems\n\n\nclass Sms(object):\n    \"\"\" Abstract SMS message base class \"\"\"\n    __metaclass__ = abc.ABCMeta\n\n    # Some constants to ease handling SMS statuses\n    STATUS_RECEIVED_UNREAD = 0\n    STATUS_RECEIVED_READ = 1\n    STATUS_STORED_UNSENT = 2\n    STATUS_STORED_SENT = 3\n    STATUS_ALL = 4\n    # ...and a handy converter for text mode statuses\n    TEXT_MODE_STATUS_MAP = {'REC UNREAD': STATUS_RECEIVED_UNREAD,\n                            'REC READ': STATUS_RECEIVED_READ,\n                            'STO UNSENT': STATUS_STORED_UNSENT,\n                            'STO SENT': STATUS_STORED_SENT,\n                            'ALL': STATUS_ALL}\n\n    def __init__(self, number, text, smsc=None):\n        self.number = number\n        self.text = text\n        self.smsc = smsc\n\n\nclass ReceivedSms(Sms):\n    \"\"\" An SMS message that has been received (MT) \"\"\"\n\n    def __init__(self, gsmModem, status, number, time, text, smsc=None, udh=[], index=None):\n        super(ReceivedSms, self).__init__(number, text, smsc)\n        self._gsmModem = weakref.proxy(gsmModem)\n        self.status = status\n        self.time = time\n        self.udh = udh\n        self.index = index\n\n    def reply(self, message):\n        \"\"\" Convenience method that sends a reply SMS to the sender of this message \"\"\"\n        return self._gsmModem.sendSms(self.number, message)\n\n    def sendSms(self, dnumber, message):\n        \"\"\" Convenience method that sends a SMS to someone else \"\"\"\n        return self._gsmModem.sendSms(dnumber, message)\n\n    def getModem(self):\n        \"\"\" Convenience method that returns the gsm modem instance \"\"\"\n        return self._gsmModem\n\nclass SentSms(Sms):\n    \"\"\" An SMS message that has been sent (MO) \"\"\"\n\n    ENROUTE = 0 # Status indicating message is still enroute to destination\n    DELIVERED = 1 # Status indicating message has been received by destination handset\n    FAILED = 2 # Status indicating message delivery has failed\n\n    def __init__(self, number, text, reference, smsc=None):\n        super(SentSms, self).__init__(number, text, smsc)\n        self.report = None # Status report for this SMS (StatusReport object)\n        self.reference = reference\n\n    @property\n    def status(self):\n        \"\"\" Status of this SMS. Can be ENROUTE, DELIVERED or FAILED\n\n        The actual status report object may be accessed via the 'report' attribute\n        if status is 'DELIVERED' or 'FAILED'\n        \"\"\"\n        if self.report == None:\n            return SentSms.ENROUTE\n        else:\n            return SentSms.DELIVERED if self.report.deliveryStatus == StatusReport.DELIVERED else SentSms.FAILED\n\n\nclass StatusReport(Sms):\n    \"\"\" An SMS status/delivery report\n\n    Note: the 'status' attribute of this class refers to this status report SM's status (whether\n    it has been read, etc). To find the status of the message that caused this status report,\n    use the 'deliveryStatus' attribute.\n    \"\"\"\n\n    DELIVERED = 0 # SMS delivery status: delivery successful\n    FAILED = 68 # SMS delivery status: delivery failed\n\n    def __init__(self, gsmModem, status, reference, number, timeSent, timeFinalized, deliveryStatus, smsc=None):\n        super(StatusReport, self).__init__(number, None, smsc)\n        self._gsmModem = weakref.proxy(gsmModem)\n        self.status = status\n        self.reference = reference\n        self.timeSent = timeSent\n        self.timeFinalized = timeFinalized\n        self.deliveryStatus = deliveryStatus\n\n\nclass GsmModem(SerialComms):\n    \"\"\" Main class for interacting with an attached GSM modem \"\"\"\n\n    log = logging.getLogger('gsmmodem.modem.GsmModem')\n\n    # Used for parsing AT command errors\n    CM_ERROR_REGEX = re.compile('^\\+(CM[ES]) ERROR: (\\d+)$')\n    # Used for parsing signal strength query responses\n    CSQ_REGEX = re.compile('^\\+CSQ:\\s*(\\d+),')\n    # Used for parsing caller ID announcements for incoming calls. Group 1 is the number\n    CLIP_REGEX = re.compile('^\\+CLIP:\\s*\"\\+{0,1}(\\d+)\",(\\d+).*$')\n    # Used for parsing own number. Group 1 is the number\n    CNUM_REGEX = re.compile('^\\+CNUM:\\s*\".*?\",\"(\\+{0,1}\\d+)\",(\\d+).*$')\n    # Used for parsing new SMS message indications\n    CMTI_REGEX = re.compile('^\\+CMTI:\\s*\"([^\"]+)\",\\s*(\\d+)$')\n    # Used for parsing SMS message reads (text mode)\n    CMGR_SM_DELIVER_REGEX_TEXT = None\n    # Used for parsing SMS status report message reads (text mode)\n    CMGR_SM_REPORT_REGEXT_TEXT = None\n    # Used for parsing SMS message reads (PDU mode)\n    CMGR_REGEX_PDU = None\n    # Used for parsing USSD event notifications\n    CUSD_REGEX = re.compile('\\+CUSD:\\s*(\\d),\\s*\"(.*?)\",\\s*(\\d+)', re.DOTALL)\n    # Used for parsing SMS status reports\n    CDSI_REGEX = re.compile('\\+CDSI:\\s*\"([^\"]+)\",(\\d+)$')\n    CDS_REGEX  = re.compile('\\+CDS:\\s*([0-9]+)\"$')\n\n    def __init__(self, port, baudrate=115200, incomingCallCallbackFunc=None, smsReceivedCallbackFunc=None, smsStatusReportCallback=None, requestDelivery=True, AT_CNMI=\"\", *a, **kw):\n        super(GsmModem, self).__init__(port, baudrate, notifyCallbackFunc=self._handleModemNotification, *a, **kw)\n        self.incomingCallCallback = incomingCallCallbackFunc or self._placeholderCallback\n        self.smsReceivedCallback = smsReceivedCallbackFunc or self._placeholderCallback\n        self.smsStatusReportCallback = smsStatusReportCallback or self._placeholderCallback\n        self.requestDelivery = requestDelivery\n        self.AT_CNMI = AT_CNMI or \"2,1,0,2\"\n        # Flag indicating whether caller ID for incoming call notification has been set up\n        self._callingLineIdentification = False\n        # Flag indicating whether incoming call notifications have extended information\n        self._extendedIncomingCallIndication = False\n        # Current active calls (ringing and/or answered), key is the unique call ID (not the remote number)\n        self.activeCalls = {}\n        # Dict containing sent SMS messages (for auto-tracking their delivery status)\n        self.sentSms = weakref.WeakValueDictionary()\n        self._ussdSessionEvent = None # threading.Event\n        self._ussdResponse = None # gsmmodem.modem.Ussd\n        self._smsStatusReportEvent = None # threading.Event\n        self._dialEvent = None # threading.Event\n        self._dialResponse = None # gsmmodem.modem.Call\n        self._waitForAtdResponse = True # Flag that controls if we should wait for an immediate response to ATD, or not\n        self._waitForCallInitUpdate = True # Flag that controls if we should wait for a ATD \"call initiated\" message\n        self._callStatusUpdates = [] # populated during connect() - contains regexes and handlers for detecting/handling call status updates\n        self._mustPollCallStatus = False # whether or not the modem must be polled for outgoing call status updates\n        self._pollCallStatusRegex = None # Regular expression used when polling outgoing call status\n        self._writeWait = 0 # Time (in seconds to wait after writing a command (adjusted when 515 errors are detected)\n        self._smsTextMode = False # Storage variable for the smsTextMode property\n        self._gsmBusy = 0 # Storage variable for the GSMBUSY property\n        self._smscNumber = None # Default SMSC number\n        self._smsRef = 0 # Sent SMS reference counter\n        self._smsMemReadDelete = None # Preferred message storage memory for reads/deletes (<mem1> parameter used for +CPMS)\n        self._smsMemWrite = None # Preferred message storage memory for writes (<mem2> parameter used for +CPMS)\n        self._smsReadSupported = True # Whether or not reading SMS messages is supported via AT commands\n        self._smsEncoding = 'GSM' # Default SMS encoding\n        self._smsSupportedEncodingNames = None # List of available encoding names\n        self._commands = None # List of supported AT commands\n        #Pool of detected DTMF\n        self.dtmfpool = []\n\n    def connect(self, pin=None, waitingForModemToStartInSeconds=0):\n        \"\"\" Opens the port and initializes the modem and SIM card\n\n        :param pin: The SIM card PIN code, if any\n        :type pin: str\n\n        :raise PinRequiredError: if the SIM card requires a PIN but none was provided\n        :raise IncorrectPinError: if the specified PIN is incorrect\n        \"\"\"\n        self.log.info('Connecting to modem on port %s at %dbps', self.port, self.baudrate)\n        super(GsmModem, self).connect()\n\n        if waitingForModemToStartInSeconds > 0:\n            while waitingForModemToStartInSeconds > 0:\n                try:\n                    self.write('AT', waitForResponse=True, timeout=0.5)\n                    break\n                except TimeoutException:\n                    waitingForModemToStartInSeconds -= 0.5\n\n        # Send some initialization commands to the modem\n        try:\n            self.write('ATZ') # reset configuration\n        except CommandError:\n            # Some modems require a SIM PIN at this stage already; unlock it now\n            # Attempt to enable detailed error messages (to catch incorrect PIN error)\n            # but ignore if it fails\n            self.write('AT+CMEE=1', parseError=False)\n            self._unlockSim(pin)\n            pinCheckComplete = True\n            self.write('ATZ') # reset configuration\n        else:\n            pinCheckComplete = False\n        self.write('ATE0') # echo off\n        try:\n            cfun = lineStartingWith('+CFUN:', self.write('AT+CFUN?'))[7:] # example response: +CFUN: 1 or +CFUN: 1,0\n            cfun = int(cfun.split(\",\")[0])\n            if cfun != 1:\n                self.write('AT+CFUN=1')\n        except CommandError:\n            pass # just ignore if the +CFUN command isn't supported\n\n        self.write('AT+CMEE=1') # enable detailed error messages (even if it has already been set - ATZ may reset this)\n        if not pinCheckComplete:\n            self._unlockSim(pin)\n\n        # Get list of supported commands from modem\n        commands = self.supportedCommands\n        self._commands = commands\n\n        # Device-specific settings\n        callUpdateTableHint = 0 # unknown modem\n        enableWind = False\n        if commands != None:\n            if '^CVOICE' in commands:\n                self.write('AT^CVOICE=0', parseError=False) # Enable voice calls\n            if '+VTS' in commands: # Check for DTMF sending support\n                Call.dtmfSupport = True\n            elif '^DTMF' in commands:\n                # Huawei modems use ^DTMF to send DTMF tones\n                callUpdateTableHint = 1 # Huawei\n            if '^USSDMODE' in commands:\n                # Enable Huawei text-mode USSD\n                self.write('AT^USSDMODE=0', parseError=False)\n            if '+WIND' in commands:\n                callUpdateTableHint = 2 # Wavecom\n                enableWind = True\n            elif '+ZPAS' in commands:\n                callUpdateTableHint = 3 # ZTE\n        else:\n            # Try to enable general notifications on Wavecom-like device\n            enableWind = True\n\n        if enableWind:\n            try:\n                wind = lineStartingWith('+WIND:', self.write('AT+WIND?')) # Check current WIND value; example response: +WIND: 63\n            except CommandError:\n                # Modem does not support +WIND notifications. See if we can detect other known call update notifications\n                pass\n            else:\n                # Enable notifications for call setup, hangup, etc\n                if int(wind[7:]) != 50:\n                    self.write('AT+WIND=50')\n                callUpdateTableHint = 2 # Wavecom\n\n        # Attempt to identify modem type directly (if not already) - for outgoing call status updates\n        if callUpdateTableHint == 0:\n            if 'simcom' in self.manufacturer.lower() : #simcom modems support DTMF and don't support AT+CLAC\n                Call.dtmfSupport = True\n                try:\n                    self.write('AT+DDET=1')                # enable detect incoming DTMF\n                except CommandError:\n                    # simcom 7000E for example doesn't support the DDET command\n                    Call.dtmfSupport = False\n\n            if self.manufacturer.lower() == 'huawei':\n                callUpdateTableHint = 1 # huawei\n            else:\n                # See if this is a ZTE modem that has not yet been identified based on supported commands\n                try:\n                    self.write('AT+ZPAS?')\n                except CommandError:\n                    pass # Not a ZTE modem\n                else:\n                    callUpdateTableHint = 3 # ZTE\n        # Load outgoing call status updates based on identified modem features\n        if callUpdateTableHint == 1:\n            # Use Hauwei's ^NOTIFICATIONs\n            self.log.info('Loading Huawei call state update table')\n            self._callStatusUpdates = ((re.compile('^\\^ORIG:(\\d),(\\d)$'), self._handleCallInitiated),\n                                       (re.compile('^\\^CONN:(\\d),(\\d)$'), self._handleCallAnswered),\n                                       (re.compile('^\\^CEND:(\\d),(\\d+),(\\d)+,(\\d)+$'), self._handleCallEnded))\n            self._mustPollCallStatus = False\n            # Huawei modems use ^DTMF to send DTMF tones; use that instead\n            Call.DTMF_COMMAND_BASE = '^DTMF={cid},'\n            Call.dtmfSupport = True\n        elif callUpdateTableHint == 2:\n            # Wavecom modem: +WIND notifications supported\n            self.log.info('Loading Wavecom call state update table')\n            self._callStatusUpdates = ((re.compile('^\\+WIND: 5,(\\d)$'), self._handleCallInitiated),\n                                      (re.compile('^OK$'), self._handleCallAnswered),\n                                      (re.compile('^\\+WIND: 6,(\\d)$'), self._handleCallEnded))\n            self._waitForAtdResponse = False # Wavecom modems return OK only when the call is answered\n            self._mustPollCallStatus = False\n            if commands == None: # older modem, assume it has standard DTMF support\n                Call.dtmfSupport = True\n        elif callUpdateTableHint == 3: # ZTE\n            # Use ZTE notifications (\"CONNECT\"/\"HANGUP\", but no \"call initiated\" notification)\n            self.log.info('Loading ZTE call state update table')\n            self._callStatusUpdates = ((re.compile('^CONNECT$'), self._handleCallAnswered),\n                                       (re.compile('^HANGUP:\\s*(\\d+)$'), self._handleCallEnded),\n                                       (re.compile('^OK$'), self._handleCallRejected))\n            self._waitForAtdResponse = False # ZTE modems do not return an immediate  OK only when the call is answered\n            self._mustPollCallStatus = False\n            self._waitForCallInitUpdate = False # ZTE modems do not provide \"call initiated\" updates\n            if commands == None: # ZTE uses standard +VTS for DTMF\n                Call.dtmfSupport = True\n        else:\n            # Unknown modem - we do not know what its call updates look like. Use polling instead\n            self.log.info('Unknown/generic modem type - will use polling for call state updates')\n            self._mustPollCallStatus = True\n            self._pollCallStatusRegex = re.compile('^\\+CLCC:\\s+(\\d+),(\\d),(\\d),(\\d),([^,]),\"([^,]*)\",(\\d+)$')\n            self._waitForAtdResponse = True # Most modems return OK immediately after issuing ATD\n\n        # General meta-information setup\n        self.write('AT+COPS=3,0', parseError=False) # Use long alphanumeric name format\n\n        # SMS setup\n        self.write('AT+CMGF={0}'.format(1 if self.smsTextMode else 0)) # Switch to text or PDU mode for SMS messages\n        self._compileSmsRegexes()\n        if self._smscNumber != None:\n            self.write('AT+CSCA=\"{0}\"'.format(self._smscNumber)) # Set default SMSC number\n            currentSmscNumber = self._smscNumber\n        else:\n            currentSmscNumber = self.smsc\n        # Some modems delete the SMSC number when setting text-mode SMS parameters; preserve it if needed\n        if currentSmscNumber != None:\n            self._smscNumber = None # clear cache\n        if self.requestDelivery:\n            self.write('AT+CSMP=49,167,0,0', parseError=False) # Enable delivery reports\n        else:\n            self.write('AT+CSMP=17,167,0,0', parseError=False) # Not enable delivery reports\n        # ...check SMSC again to ensure it did not change\n        if currentSmscNumber != None and self.smsc != currentSmscNumber:\n            self.smsc = currentSmscNumber\n\n        # Set message storage, but first check what the modem supports - example response: +CPMS: ((\"SM\",\"BM\",\"SR\"),(\"SM\"))\n        try:\n            cpmsLine = lineStartingWith('+CPMS', self.write('AT+CPMS=?'))\n        except CommandError:\n            # Modem does not support AT+CPMS; SMS reading unavailable\n            self._smsReadSupported = False\n            self.log.warning('SMS preferred message storage query not supported by modem. SMS reading unavailable.')\n        else:\n            cpmsSupport = cpmsLine.split(' ', 1)[1].split('),(')\n            # Do a sanity check on the memory types returned - Nokia S60 devices return empty strings, for example\n            for memItem in cpmsSupport:\n                if len(memItem) == 0:\n                    # No support for reading stored SMS via AT commands - probably a Nokia S60\n                    self._smsReadSupported = False\n                    self.log.warning('Invalid SMS message storage support returned by modem. SMS reading unavailable. Response was: \"%s\"', cpmsLine)\n                    break\n            else:\n                # Suppported memory types look fine, continue\n                preferredMemoryTypes = ('\"ME\"', '\"SM\"', '\"SR\"')\n                cpmsItems = [''] * len(cpmsSupport)\n                for i in xrange(len(cpmsSupport)):\n                    for memType in preferredMemoryTypes:\n                        if memType in cpmsSupport[i]:\n                            if i == 0:\n                                self._smsMemReadDelete = memType\n                            cpmsItems[i] = memType\n                            break\n                self.write('AT+CPMS={0}'.format(','.join(cpmsItems))) # Set message storage\n            del cpmsSupport\n            del cpmsLine\n\n        if self._smsReadSupported and (self.smsReceivedCallback or self.smsStatusReportCallback):\n            try:\n                self.write('AT+CNMI=' + self.AT_CNMI)  # Set message notifications\n            except CommandError:\n                try:\n                    self.write('AT+CNMI=2,1,0,1,0') # Set message notifications, using TE for delivery reports <ds>\n                except CommandError:\n                    # Message notifications not supported\n                    self._smsReadSupported = False\n                    self.log.warning('Incoming SMS notifications not supported by modem. SMS receiving unavailable.')\n\n        # Incoming call notification setup\n        try:\n            self.write('AT+CLIP=1') # Enable calling line identification presentation\n        except CommandError as clipError:\n            self._callingLineIdentification = False\n            self.log.warning('Incoming call calling line identification (caller ID) not supported by modem. Error: {0}'.format(clipError))\n        else:\n            self._callingLineIdentification = True\n            try:\n                self.write('AT+CRC=1') # Enable extended format of incoming indication (optional)\n            except CommandError as crcError:\n                self._extendedIncomingCallIndication = False\n                self.log.warning('Extended format incoming call indication not supported by modem. Error: {0}'.format(crcError))\n            else:\n                self._extendedIncomingCallIndication = True\n\n        # Call control setup\n        self.write('AT+CVHU=0', parseError=False) # Enable call hang-up with ATH command (ignore if command not supported)\n\n    def _unlockSim(self, pin):\n        \"\"\" Unlocks the SIM card using the specified PIN (if necessary, else does nothing) \"\"\"\n        # Unlock the SIM card if needed\n        try:\n            cpinResponse = lineStartingWith('+CPIN', self.write('AT+CPIN?', timeout=15))\n        except TimeoutException as timeout:\n            # Wavecom modems do not end +CPIN responses with \"OK\" (github issue #19) - see if just the +CPIN response was returned\n            if timeout.data != None:\n                cpinResponse = lineStartingWith('+CPIN', timeout.data)\n                if cpinResponse == None:\n                    # No useful response read\n                    raise timeout\n            else:\n                # Nothing read (real timeout)\n                raise timeout\n        if cpinResponse != '+CPIN: READY':\n            if pin != None:\n                self.write('AT+CPIN=\"{0}\"'.format(pin))\n            else:\n                raise PinRequiredError('AT+CPIN')\n\n    def write(self, data, waitForResponse=True, timeout=10, parseError=True, writeTerm=TERMINATOR, expectedResponseTermSeq=None):\n        \"\"\" Write data to the modem.\n\n        This method adds the ``\\\\r\\\\n`` end-of-line sequence to the data parameter, and\n        writes it to the modem.\n\n        :param data: Command/data to be written to the modem\n        :type data: str\n        :param waitForResponse: Whether this method should block and return the response from the modem or not\n        :type waitForResponse: bool\n        :param timeout: Maximum amount of time in seconds to wait for a response from the modem\n        :type timeout: int\n        :param parseError: If True, a CommandError is raised if the modem responds with an error (otherwise the response is returned as-is)\n        :type parseError: bool\n        :param writeTerm: The terminating sequence to append to the written data\n        :type writeTerm: str\n        :param expectedResponseTermSeq: The expected terminating sequence that marks the end of the modem's response (defaults to ``\\\\r\\\\n``)\n        :type expectedResponseTermSeq: str\n\n        :raise CommandError: if the command returns an error (only if parseError parameter is True)\n        :raise TimeoutException: if no response to the command was received from the modem\n\n        :return: A list containing the response lines from the modem, or None if waitForResponse is False\n        :rtype: list\n        \"\"\"\n\n        self.log.debug('write: %s', data)\n        responseLines = super(GsmModem, self).write(data + writeTerm, waitForResponse=waitForResponse, timeout=timeout, expectedResponseTermSeq=expectedResponseTermSeq)\n        if self._writeWait > 0: # Sleep a bit if required (some older modems suffer under load)\n            time.sleep(self._writeWait)\n        if waitForResponse:\n            cmdStatusLine = responseLines[-1]\n            if parseError:\n                if 'ERROR' in cmdStatusLine:\n                    cmErrorMatch = self.CM_ERROR_REGEX.match(cmdStatusLine)\n                    if cmErrorMatch:\n                        errorType = cmErrorMatch.group(1)\n                        errorCode = int(cmErrorMatch.group(2))\n                        if errorCode == 515 or errorCode == 14:\n                            # 515 means: \"Please wait, init or command processing in progress.\"\n                            # 14 means \"SIM busy\"\n                            self._writeWait += 0.2 # Increase waiting period temporarily\n                            # Retry the command after waiting a bit\n                            self.log.debug('Device/SIM busy error detected; self._writeWait adjusted to %fs', self._writeWait)\n                            time.sleep(self._writeWait)\n                            result = self.write(data, waitForResponse, timeout, parseError, writeTerm, expectedResponseTermSeq)\n                            self.log.debug('self_writeWait set to 0.1 because of recovering from device busy (515) error')\n                            if errorCode == 515:\n                                self._writeWait = 0.1 # Set this to something sane for further commands (slow modem)\n                            else:\n                                self._writeWait = 0 # The modem was just waiting for the SIM card\n                            return result\n                        if errorType == 'CME':\n                            raise CmeError(data, int(errorCode))\n                        else: # CMS error\n                            raise CmsError(data, int(errorCode))\n                    else:\n                        raise CommandError(data)\n                elif cmdStatusLine == 'COMMAND NOT SUPPORT': # Some Huawei modems respond with this for unknown commands\n                    raise CommandError('{} ({})'.format(data,cmdStatusLine))\n            return responseLines\n\n    @property\n    def signalStrength(self):\n        \"\"\" Checks the modem's cellular network signal strength\n\n        :raise CommandError: if an error occurs\n\n        :return: The network signal strength as an integer between 0 and 99, or -1 if it is unknown\n        :rtype: int\n        \"\"\"\n        csq = self.CSQ_REGEX.match(self.write('AT+CSQ')[0])\n        if csq:\n            ss = int(csq.group(1))\n            return ss if ss != 99 else -1\n        else:\n            raise CommandError()\n\n    @property\n    def manufacturer(self):\n        \"\"\" :return: The modem's manufacturer's name \"\"\"\n        return self.write('AT+CGMI')[0]\n\n    @property\n    def model(self):\n        \"\"\" :return: The modem's model name \"\"\"\n        return self.write('AT+CGMM')[0]\n\n    @property\n    def revision(self):\n        \"\"\" :return: The modem's software revision, or None if not known/supported \"\"\"\n        try:\n            return self.write('AT+CGMR')[0]\n        except CommandError:\n            return None\n\n    @property\n    def imei(self):\n        \"\"\" :return: The modem's serial number (IMEI number) \"\"\"\n        return self.write('AT+CGSN')[0]\n\n    @property\n    def imsi(self):\n        \"\"\" :return: The IMSI (International Mobile Subscriber Identity) of the SIM card. The PIN may need to be entered before reading the IMSI \"\"\"\n        return self.write('AT+CIMI')[0]\n\n    @property\n    def networkName(self):\n        \"\"\" :return: the name of the GSM Network Operator to which the modem is connected \"\"\"\n        copsMatch = lineMatching('^\\+COPS: (\\d),(\\d),\"(.+)\",{0,1}\\d*$', self.write('AT+COPS?')) # response format: +COPS: mode,format,\"operator_name\",x\n        if copsMatch:\n            return copsMatch.group(3)\n\n    @property\n    def supportedCommands(self):\n        \"\"\" :return: list of AT commands supported by this modem (without the AT prefix). Returns None if not known \"\"\"\n        try:\n            # AT+CLAC responses differ between modems. Most respond with +CLAC: and then a comma-separated list of commands\n            # while others simply return each command on a new line, with no +CLAC: prefix\n            response = self.write('AT+CLAC', timeout=10)\n            if len(response) == 2: # Single-line response, comma separated\n                commands = response[0]\n                if commands.startswith('+CLAC'):\n                    commands = commands[6:] # remove the +CLAC: prefix before splitting\n                return commands.split(',')\n            elif len(response) > 2: # Multi-line response\n                return [removeAtPrefix(cmd.strip()) for cmd in response[:-1]]\n            else:\n                self.log.debug('Unhandled +CLAC response: {0}'.format(response))\n                return None\n        except (TimeoutException, CommandError):\n            # Try interactive command recognition\n            commands = []\n            checkable_commands = ['^CVOICE', '+VTS', '^DTMF', '^USSDMODE', '+WIND', '+ZPAS', '+CSCS', '+CNUM']\n\n            # Check if modem is still alive\n            try:\n                response = self.write('AT')\n            except:\n                raise TimeoutException\n\n            # Check all commands that will by considered\n            for command in checkable_commands:\n                try:\n                    # Compose AT command that will read values under specified function\n                    at_command='AT'+command+'=?'\n                    response = self.write(at_command)\n                    # If there are values inside response - add command to the list\n                    commands.append(command)\n                except:\n                    continue\n\n            # Return found commands\n            if len(commands) == 0:\n                return None\n            else:\n                return commands\n\n    @property\n    def smsTextMode(self):\n        \"\"\" :return: True if the modem is set to use text mode for SMS, False if it is set to use PDU mode \"\"\"\n        return self._smsTextMode\n    @smsTextMode.setter\n    def smsTextMode(self, textMode):\n        \"\"\" Set to True for the modem to use text mode for SMS, or False for it to use PDU mode \"\"\"\n        if textMode != self._smsTextMode:\n            if self.alive:\n                self.write('AT+CMGF={0}'.format(1 if textMode else 0))\n            self._smsTextMode = textMode\n            self._compileSmsRegexes()\n\n    @property\n    def smsSupportedEncoding(self):\n        \"\"\"\n        :raise NotImplementedError: If an error occures during AT command response parsing.\n        :return: List of supported encoding names. \"\"\"\n\n        # Check if command is available\n        if self._commands == None:\n            self._commands = self.supportedCommands\n\n        if self._commands == None:\n            self._smsSupportedEncodingNames = []\n            return self._smsSupportedEncodingNames\n\n        if not '+CSCS' in self._commands:\n            self._smsSupportedEncodingNames = []\n            return self._smsSupportedEncodingNames\n\n        # Get available encoding names\n        response = self.write('AT+CSCS=?')\n\n        # Check response length (should be 2 - list of options and command status)\n        if len(response) != 2:\n            self.log.debug('Unhandled +CSCS response: {0}'.format(response))\n            self._smsSupportedEncodingNames = []\n            raise NotImplementedError\n\n        # Extract encoding names list\n        try:\n            enc_list = response[0]  # Get the first line\n            enc_list = enc_list[6:] # Remove '+CSCS: ' prefix\n            # Extract AT list in format (\"str\", \"str2\", \"str3\")\n            enc_list = enc_list.split('(')[1]\n            enc_list = enc_list.split(')')[0]\n            enc_list = enc_list.split(',')\n            enc_list = [x.split('\"')[1] for x in enc_list]\n        except:\n            self.log.debug('Unhandled +CSCS response: {0}'.format(response))\n            self._smsSupportedEncodingNames = []\n            raise NotImplementedError\n\n        self._smsSupportedEncodingNames = enc_list\n        return self._smsSupportedEncodingNames\n\n    @property\n    def smsEncoding(self):\n        \"\"\" :return: Encoding name if encoding command is available, else GSM. \"\"\"\n        if self._commands == None:\n            self._commands = self.supportedCommands\n\n        if self._commands == None:\n            return self._smsEncoding\n\n        if '+CSCS' in self._commands:\n            response = self.write('AT+CSCS?')\n\n            if len(response) == 2:\n                encoding = response[0]\n                if encoding.startswith('+CSCS'):\n                    encoding = encoding[6:].split('\"') # remove the +CSCS: prefix before splitting\n                    if len(encoding) == 3:\n                        self._smsEncoding = encoding[1]\n                    else:\n                        self.log.debug('Unhandled +CSCS response: {0}'.format(response))\n            else:\n                self.log.debug('Unhandled +CSCS response: {0}'.format(response))\n\n        return self._smsEncoding\n    @smsEncoding.setter\n    def smsEncoding(self, encoding):\n        \"\"\" Set encoding for SMS inside PDU mode.\n\n        :raise CommandError: if unable to set encoding\n        :raise ValueError: if encoding is not supported by modem\n        \"\"\"\n        # Check if command is available\n        if self._commands == None:\n            self._commands = self.supportedCommands\n\n        if self._commands == None:\n            if encoding != self._smsEncoding:\n                raise CommandError('Unable to set SMS encoding (no supported commands)')\n            else:\n                return\n\n        if not '+CSCS' in self._commands:\n            if encoding != self._smsEncoding:\n                raise CommandError('Unable to set SMS encoding (+CSCS command not supported)')\n            else:\n                return\n\n        # Check if command is available\n        if self._smsSupportedEncodingNames == None:\n            self.smsSupportedEncoding\n\n        # Check if desired encoding is available\n        if encoding in self._smsSupportedEncodingNames:\n            # Set encoding\n            response = self.write('AT+CSCS=\"{0}\"'.format(encoding))\n            if len(response) == 1:\n                if response[0].lower() == 'ok':\n                    self._smsEncoding = encoding\n                    return\n\n        if encoding != self._smsEncoding:\n            raise ValueError('Unable to set SMS encoding (enocoding {0} not supported)'.format(encoding))\n        else:\n            return\n\n    def _setSmsMemory(self, readDelete=None, write=None):\n        \"\"\" Set the current SMS memory to use for read/delete/write operations \"\"\"\n        # Switch to the correct memory type if required\n        if write != None and write != self._smsMemWrite:\n            readDel = readDelete or self._smsMemReadDelete\n            self.write('AT+CPMS=\"{0}\",\"{1}\"'.format(readDel, write))\n            self._smsMemReadDelete = readDel\n            self._smsMemWrite = write\n        elif readDelete != None and readDelete != self._smsMemReadDelete:\n            self.write('AT+CPMS=\"{0}\"'.format(readDelete))\n            self._smsMemReadDelete = readDelete\n\n    def _compileSmsRegexes(self):\n        \"\"\" Compiles regular expression used for parsing SMS messages based on current mode \"\"\"\n        if self.smsTextMode:\n            if self.CMGR_SM_DELIVER_REGEX_TEXT == None:\n                self.CMGR_SM_DELIVER_REGEX_TEXT = re.compile('^\\+CMGR: \"([^\"]+)\",\"([^\"]+)\",[^,]*,\"([^\"]+)\"$')\n                self.CMGR_SM_REPORT_REGEXT_TEXT = re.compile('^\\+CMGR: ([^,]*),\\d+,(\\d+),\"{0,1}([^\"]*)\"{0,1},\\d*,\"([^\"]+)\",\"([^\"]+)\",(\\d+)$')\n        elif self.CMGR_REGEX_PDU == None:\n            self.CMGR_REGEX_PDU = re.compile('^\\+CMGR:\\s*(\\d*),\\s*\"{0,1}([^\"]*)\"{0,1},\\s*(\\d+)$')\n\n    @property\n    def gsmBusy(self):\n        \"\"\" :return: Current GSMBUSY state \"\"\"\n        try:\n            response = self.write('AT+GSMBUSY?')\n            response = response[0] # Get the first line\n            response = response[10] # Remove '+GSMBUSY: ' prefix\n            self._gsmBusy = response\n        except:\n            pass # If error is related to ME funtionality: +CME ERROR: <error>\n        return self._gsmBusy\n    @gsmBusy.setter\n    def gsmBusy(self, gsmBusy):\n        \"\"\" Sete GSMBUSY state \"\"\"\n        if gsmBusy != self._gsmBusy:\n            if self.alive:\n                self.write('AT+GSMBUSY=\"{0}\"'.format(gsmBusy))\n            self._gsmBusy = gsmBusy\n\n    @property\n    def smsc(self):\n        \"\"\" :return: The default SMSC number stored on the SIM card \"\"\"\n        if self._smscNumber == None:\n            try:\n                readSmsc = self.write('AT+CSCA?')\n            except SmscNumberUnknownError:\n                pass # Some modems return a CMS 330 error if the value isn't set\n            else:\n                cscaMatch = lineMatching('\\+CSCA:\\s*\"([^,]+)\",(\\d+)$', readSmsc)\n                if cscaMatch:\n                    self._smscNumber = cscaMatch.group(1)\n        return self._smscNumber\n    @smsc.setter\n    def smsc(self, smscNumber):\n        \"\"\" Set the default SMSC number to use when sending SMS messages \"\"\"\n        if smscNumber != self._smscNumber:\n            if self.alive:\n                self.write('AT+CSCA=\"{0}\"'.format(smscNumber))\n            self._smscNumber = smscNumber\n\n    @property\n    def ownNumber(self):\n        \"\"\" Query subscriber phone number.\n\n        It must be stored on SIM by operator.\n        If is it not stored already, it usually is possible to store the number by user.\n\n                :raise TimeoutException: if a timeout was specified and reached\n\n\n        :return: Subscriber SIM phone number. Returns None if not known\n        :rtype: int\n        \"\"\"\n\n        try:\n            if \"+CNUM\" in self._commands:\n                response = self.write('AT+CNUM')\n            else:\n                # temporarily switch to \"own numbers\" phonebook, read position 1 and than switch back\n                response = self.write('AT+CPBS?')\n                selected_phonebook = response[0][6:].split('\"')[1] # first line, remove the +CSCS: prefix, split, first parameter\n\n                if selected_phonebook is not \"ON\":\n                    self.write('AT+CPBS=\"ON\"')\n\n                response = self.write(\"AT+CPBR=1\")\n                self.write('AT+CPBS=\"{0}\"'.format(selected_phonebook))\n\n            if response is \"OK\": # command is supported, but no number is set\n                return None\n            elif len(response) == 2: # OK and phone number. Actual number is in the first line, second parameter, and is placed inside quotation marks\n                cnumLine = response[0]\n                cnumMatch = self.CNUM_REGEX.match(cnumLine)\n                if cnumMatch:\n                    return cnumMatch.group(1)\n                else:\n                    self.log.debug('Error parse +CNUM response: {0}'.format(response))\n                    return None\n            elif len(response) > 2: # Multi-line response\n                self.log.debug('Unhandled +CNUM/+CPBS response: {0}'.format(response))\n                return None\n\n        except (TimeoutException, CommandError):\n            raise\n\n    @ownNumber.setter\n    def ownNumber(self, phone_number):\n        actual_phonebook = self.write('AT+CPBS?')\n        if actual_phonebook is not \"ON\":\n            self.write('AT+CPBS=\"ON\"')\n        self.write('AT+CPBW=1,\"' + phone_number + '\"')\n\n\n    def waitForNetworkCoverage(self, timeout=None):\n        \"\"\" Block until the modem has GSM network coverage.\n\n        This method blocks until the modem is registered with the network\n        and the signal strength is greater than 0, optionally timing out\n        if a timeout was specified\n\n        :param timeout: Maximum time to wait for network coverage, in seconds\n        :type timeout: int or float\n\n        :raise TimeoutException: if a timeout was specified and reached\n        :raise InvalidStateException: if the modem is not going to receive network coverage (SIM blocked, etc)\n\n        :return: the current signal strength\n        \"\"\"\n        block = [True]\n        if timeout != None:\n            # Set up a timeout mechanism\n            def _cancelBlock():\n                block[0] = False\n            t = threading.Timer(timeout, _cancelBlock)\n            t.start()\n        ss = -1\n        checkCreg = True\n        while block[0]:\n            if checkCreg:\n                cregResult = lineMatching('^\\+CREG:\\s*(\\d),(\\d)(,[^,]*,[^,]*)?$', self.write('AT+CREG?', parseError=False)) # example result: +CREG: 0,1\n                if cregResult:\n                    status = int(cregResult.group(2))\n                    if status in (1, 5):\n                        # 1: registered, home network, 5: registered, roaming\n                        # Now simply check and return network signal strength\n                        checkCreg = False\n                    elif status == 3:\n                        raise InvalidStateException('Network registration denied')\n                    elif status == 0:\n                        raise InvalidStateException('Device not searching for network operator')\n                else:\n                    # Disable network registration check; only use signal strength\n                    self.log.info('+CREG check disabled due to invalid response or unsupported command')\n                    checkCreg = False\n            else:\n                # Check signal strength\n                ss = self.signalStrength\n                if ss > 0:\n                    return ss\n            time.sleep(1)\n        else:\n            # If this is reached, the timer task has triggered\n            raise TimeoutException()\n\n    def sendSms(self, destination, text, waitForDeliveryReport=False, deliveryTimeout=15, sendFlash=False):\n        \"\"\" Send an SMS text message\n\n        :param destination: the recipient's phone number\n        :type destination: str\n        :param text: the message text\n        :type text: str\n        :param waitForDeliveryReport: if True, this method blocks until a delivery report is received for the sent message\n        :type waitForDeliveryReport: boolean\n        :param deliveryTimeout: the maximum time in seconds to wait for a delivery report (if \"waitForDeliveryReport\" is True)\n        :type deliveryTimeout: int or float\n\n        :raise CommandError: if an error occurs while attempting to send the message\n        :raise TimeoutException: if the operation times out\n        \"\"\"\n\n        # Check input text to select appropriate mode (text or PDU)\n        if self.smsTextMode:\n            try:\n                encodedText = encodeTextMode(text)\n            except ValueError:\n                self.smsTextMode = False\n\n        if self.smsTextMode:\n            # Send SMS via AT commands\n            self.write('AT+CMGS=\"{0}\"'.format(destination), timeout=5, expectedResponseTermSeq='> ')\n            result = lineStartingWith('+CMGS:', self.write(text, timeout=35, writeTerm=CTRLZ))\n        else:\n            # Check encoding\n            try:\n                encodedText = encodeGsm7(text)\n            except ValueError:\n                encodedText = None\n\n            # Set GSM modem SMS encoding format\n            # Encode message text and set data coding scheme based on text contents\n            if encodedText == None:\n                # Cannot encode text using GSM-7; use UCS2 instead\n                self.smsEncoding = 'UCS2'\n            else:\n                self.smsEncoding = 'GSM'\n\n            # Encode text into PDUs\n            pdus = encodeSmsSubmitPdu(destination, text, reference=self._smsRef, sendFlash=sendFlash)\n\n            # Send SMS PDUs via AT commands\n            for pdu in pdus:\n                self.write('AT+CMGS={0}'.format(pdu.tpduLength), timeout=5, expectedResponseTermSeq='> ')\n                result = lineStartingWith('+CMGS:', self.write(str(pdu), timeout=35, writeTerm=CTRLZ)) # example: +CMGS: xx\n\n        if result == None:\n            raise CommandError('Modem did not respond with +CMGS response')\n\n        # Keep SMS reference number in order to pair delivery reports with sent message\n        reference = int(result[7:])\n        self._smsRef = reference + 1\n        if self._smsRef > 255:\n            self._smsRef = 0\n\n        # Create sent SMS object for future delivery checks\n        sms = SentSms(destination, text, reference)\n\n        # Add a weak-referenced entry for this SMS (allows us to update the SMS state if a status report is received)\n        self.sentSms[reference] = sms\n        if waitForDeliveryReport:\n            self._smsStatusReportEvent = threading.Event()\n            if self._smsStatusReportEvent.wait(deliveryTimeout):\n                self._smsStatusReportEvent = None\n            else: # Response timed out\n                self._smsStatusReportEvent = None\n                raise TimeoutException()\n        return sms\n\n    def sendUssd(self, ussdString, responseTimeout=15):\n        \"\"\" Starts a USSD session by dialing the the specified USSD string, or \\\n        sends the specified string in the existing USSD session (if any)\n\n        :param ussdString: The USSD access number to dial\n        :param responseTimeout: Maximum time to wait a response, in seconds\n\n        :raise TimeoutException: if no response is received in time\n\n        :return: The USSD response message/session (as a Ussd object)\n        :rtype: gsmmodem.modem.Ussd\n        \"\"\"\n        self._ussdSessionEvent = threading.Event()\n        try:\n            cusdResponse = self.write('AT+CUSD=1,\"{0}\",15'.format(ussdString), timeout=responseTimeout) # Should respond with \"OK\"\n        except Exception:\n            self._ussdSessionEvent = None # Cancel the thread sync lock\n            raise\n\n        # Some modems issue the +CUSD response before the acknowledgment \"OK\" - check for that\n        if len(cusdResponse) > 1:\n            cusdResponseFound = lineStartingWith('+CUSD', cusdResponse) != None\n            if cusdResponseFound:\n                self._ussdSessionEvent = None # Cancel thread sync lock\n                return self._parseCusdResponse(cusdResponse)\n        # Wait for the +CUSD notification message\n        if self._ussdSessionEvent.wait(responseTimeout):\n            self._ussdSessionEvent = None\n            return self._ussdResponse\n        else: # Response timed out\n            self._ussdSessionEvent = None\n            raise TimeoutException()\n\n\n    def checkForwarding(self, querytype, responseTimeout=15):\n        \"\"\" Check forwarding status: 0=Unconditional, 1=Busy, 2=NoReply, 3=NotReach, 4=AllFwd, 5=AllCondFwd\n        :param querytype: The type of forwarding to check\n\n        :return: Status\n        :rtype: Boolean\n        \"\"\"\n        try:\n            queryResponse = self.write('AT+CCFC={0},2'.format(querytype), timeout=responseTimeout) # Should respond with \"OK\"\n        except Exception:\n            raise\n        print(queryResponse)\n        return True\n\n\n    def setForwarding(self, fwdType, fwdEnable, fwdNumber, responseTimeout=15):\n        \"\"\" Check forwarding status: 0=Unconditional, 1=Busy, 2=NoReply, 3=NotReach, 4=AllFwd, 5=AllCondFwd\n        :param fwdType: The type of forwarding to set\n        :param fwdEnable: 1 to enable, 0 to disable, 2 to query, 3 to register, 4 to erase\n        :param fwdNumber: Number to forward to\n\n        :return: Success or not\n        :rtype: Boolean\n        \"\"\"\n        try:\n            queryResponse = self.write('AT+CCFC={0},{1},\"{2}\"'.format(fwdType, fwdEnable, fwdNumber), timeout=responseTimeout) # Should respond with \"OK\"\n        except Exception:\n            raise\n            return False\n        print(queryResponse)\n        return queryResponse\n\n    def dial(self, number, timeout=5, callStatusUpdateCallbackFunc=None):\n        \"\"\" Calls the specified phone number using a voice phone call\n\n        :param number: The phone number to dial\n        :param timeout: Maximum time to wait for the call to be established\n        :param callStatusUpdateCallbackFunc: Callback function that is executed if the call's status changes due to\n               remote events (i.e. when it is answered, the call is ended by the remote party)\n\n        :return: The outgoing call\n        :rtype: gsmmodem.modem.Call\n        \"\"\"\n        if self._waitForCallInitUpdate:\n            # Wait for the \"call originated\" notification message\n            self._dialEvent = threading.Event()\n            try:\n                self.write('ATD{0};'.format(number), timeout=timeout, waitForResponse=self._waitForAtdResponse)\n            except Exception:\n                self._dialEvent = None # Cancel the thread sync lock\n                raise\n        else:\n            # Don't wait for a call init update - base the call ID on the number of active calls\n            self.write('ATD{0};'.format(number), timeout=timeout, waitForResponse=self._waitForAtdResponse)\n            self.log.debug(\"Not waiting for outgoing call init update message\")\n            callId = len(self.activeCalls) + 1\n            callType = 0 # Assume voice\n            call = Call(self, callId, callType, number, callStatusUpdateCallbackFunc)\n            self.activeCalls[callId] = call\n            return call\n\n        if self._mustPollCallStatus:\n            # Fake a call notification by polling call status until the status indicates that the call is being dialed\n            threading.Thread(target=self._pollCallStatus, kwargs={'expectedState': 0, 'timeout': timeout}).start()\n\n        if self._dialEvent.wait(timeout):\n            self._dialEvent = None\n            callId, callType = self._dialResponse\n            call = Call(self, callId, callType, number, callStatusUpdateCallbackFunc)\n            self.activeCalls[callId] = call\n            return call\n        else: # Call establishing timed out\n            self._dialEvent = None\n            raise TimeoutException()\n\n    def processStoredSms(self, unreadOnly=False):\n        \"\"\" Process all SMS messages currently stored on the device/SIM card.\n\n        Reads all (or just unread) received SMS messages currently stored on the\n        device/SIM card, initiates \"SMS received\" events for them, and removes\n        them from the SIM card.\n        This is useful if SMS messages were received during a period that\n        python-gsmmodem was not running but the modem was powered on.\n\n        :param unreadOnly: If True, only process unread SMS messages\n        :type unreadOnly: boolean\n        \"\"\"\n        if self.smsReceivedCallback:\n            states = [Sms.STATUS_RECEIVED_UNREAD]\n            if not unreadOnly:\n                states.insert(0, Sms.STATUS_RECEIVED_READ)\n            for msgStatus in states:\n                messages = self.listStoredSms(status=msgStatus, delete=True)\n                for sms in messages:\n                    self.smsReceivedCallback(sms)\n        else:\n            raise ValueError('GsmModem.smsReceivedCallback not set')\n\n    def listStoredSms(self, status=Sms.STATUS_ALL, memory=None, delete=False):\n        \"\"\" Returns SMS messages currently stored on the device/SIM card.\n\n        The messages are read from the memory set by the \"memory\" parameter.\n\n        :param status: Filter messages based on this read status; must be 0-4 (see Sms class)\n        :type status: int\n        :param memory: The memory type to read from. If None, use the current default SMS read memory\n        :type memory: str or None\n        :param delete: If True, delete returned messages from the device/SIM card\n        :type delete: bool\n\n        :return: A list of Sms objects containing the messages read\n        :rtype: list\n        \"\"\"\n        self._setSmsMemory(readDelete=memory)\n        messages = []\n        delMessages = set()\n        if self.smsTextMode:\n            cmglRegex= re.compile('^\\+CMGL: (\\d+),\"([^\"]+)\",\"([^\"]+)\",[^,]*,\"([^\"]+)\"$')\n            for key, val in dictItemsIter(Sms.TEXT_MODE_STATUS_MAP):\n                if status == val:\n                    statusStr = key\n                    break\n            else:\n                raise ValueError('Invalid status value: {0}'.format(status))\n            result = self.write('AT+CMGL=\"{0}\"'.format(statusStr))\n            msgLines = []\n            msgIndex = msgStatus = number = msgTime = None\n            for line in result:\n                cmglMatch = cmglRegex.match(line)\n                if cmglMatch:\n                    # New message; save old one if applicable\n                    if msgIndex != None and len(msgLines) > 0:\n                        msgText = '\\n'.join(msgLines)\n                        msgLines = []\n                        messages.append(ReceivedSms(self, Sms.TEXT_MODE_STATUS_MAP[msgStatus], number, parseTextModeTimeStr(msgTime), msgText, None, [], msgIndex))\n                        delMessages.add(int(msgIndex))\n                    msgIndex, msgStatus, number, msgTime = cmglMatch.groups()\n                    msgLines = []\n                else:\n                    if line != 'OK':\n                        msgLines.append(line)\n            if msgIndex != None and len(msgLines) > 0:\n                msgText = '\\n'.join(msgLines)\n                msgLines = []\n                messages.append(ReceivedSms(self, Sms.TEXT_MODE_STATUS_MAP[msgStatus], number, parseTextModeTimeStr(msgTime), msgText, None, [], msgIndex))\n                delMessages.add(int(msgIndex))\n        else:\n            cmglRegex = re.compile('^\\+CMGL:\\s*(\\d+),\\s*(\\d+),.*$')\n            readPdu = False\n            result = self.write('AT+CMGL={0}'.format(status))\n            for line in result:\n                if not readPdu:\n                    cmglMatch = cmglRegex.match(line)\n                    if cmglMatch:\n                        msgIndex = int(cmglMatch.group(1))\n                        msgStat = int(cmglMatch.group(2))\n                        readPdu = True\n                else:\n                    try:\n                        smsDict = decodeSmsPdu(line)\n                    except EncodingError:\n                        self.log.debug('Discarding line from +CMGL response: %s', line)\n                    except:\n                        pass\n                        # dirty fix warning: https://github.com/yuriykashin/python-gsmmodem/issues/1\n                        # todo: make better fix\n                    else:\n                        if smsDict['type'] == 'SMS-DELIVER':\n                            sms = ReceivedSms(self, int(msgStat), smsDict['number'], smsDict['time'], smsDict['text'], smsDict['smsc'], smsDict.get('udh', []), msgIndex)\n                        elif smsDict['type'] == 'SMS-STATUS-REPORT':\n                            sms = StatusReport(self, int(msgStat), smsDict['reference'], smsDict['number'], smsDict['time'], smsDict['discharge'], smsDict['status'])\n                        else:\n                            raise CommandError('Invalid PDU type for readStoredSms(): {0}'.format(smsDict['type']))\n                        messages.append(sms)\n                        delMessages.add(msgIndex)\n                        readPdu = False\n        if delete:\n            if status == Sms.STATUS_ALL:\n                # Delete all messages\n                self.deleteMultipleStoredSms()\n            else:\n                for msgIndex in delMessages:\n                    self.deleteStoredSms(msgIndex)\n        return messages\n\n    def _handleModemNotification(self, lines):\n        \"\"\" Handler for unsolicited notifications from the modem\n\n        This method simply spawns a separate thread to handle the actual notification\n        (in order to release the read thread so that the handlers are able to write back to the modem, etc)\n\n        :param lines The lines that were read\n        \"\"\"\n        threading.Thread(target=self.__threadedHandleModemNotification, kwargs={'lines': lines}).start()\n\n    def __threadedHandleModemNotification(self, lines):\n        \"\"\" Implementation of _handleModemNotification() to be run in a separate thread\n\n        :param lines The lines that were read\n        \"\"\"\n        next_line_is_te_statusreport = False\n        for line in lines:\n            if 'RING' in line:\n                # Incoming call (or existing call is ringing)\n                self._handleIncomingCall(lines)\n                return\n            elif line.startswith('+CMTI'):\n                # New SMS message indication\n                self._handleSmsReceived(line)\n                return\n            elif line.startswith('+CUSD'):\n                # USSD notification - either a response or a MT-USSD (\"push USSD\") message\n                self._handleUssd(lines)\n                return\n            elif line.startswith('+CDSI'):\n                # SMS status report\n                self._handleSmsStatusReport(line)\n                return\n            elif line.startswith('+CDS'):\n                # SMS status report at next line\n                next_line_is_te_statusreport = True\n                cdsMatch = self.CDS_REGEX.match(line)\n                if cdsMatch:\n                    next_line_is_te_statusreport_length = int(cdsMatch.group(1))\n                else:\n                    next_line_is_te_statusreport_length = -1\n            elif next_line_is_te_statusreport:\n                self._handleSmsStatusReportTe(next_line_is_te_statusreport_length, line)\n                return\n            elif line.startswith('+DTMF'):\n                # New incoming DTMF\n                self._handleIncomingDTMF(line)\n                return\n            else:\n                # Check for call status updates\n                for updateRegex, handlerFunc in self._callStatusUpdates:\n                    match = updateRegex.match(line)\n                    if match:\n                        # Handle the update\n                        handlerFunc(match)\n                        return\n        # If this is reached, the notification wasn't handled\n        self.log.debug('Unhandled unsolicited modem notification: %s', lines)\n\n    #Simcom modem able detect incoming DTMF\n    def _handleIncomingDTMF(self,line):\n        self.log.debug('Handling incoming DTMF')\n\n        try:\n            dtmf_num=line.split(':')[1].replace(\" \",\"\")\n            self.dtmfpool.append(dtmf_num)\n            self.log.debug('DTMF number is {0}'.format(dtmf_num))\n        except:\n            self.log.debug('Error parse DTMF number on line {0}'.format(line))\n    def GetIncomingDTMF(self):\n        if (len(self.dtmfpool)==0):\n            return None\n        else:\n            return self.dtmfpool.pop(0)\n\n    def _handleIncomingCall(self, lines):\n        self.log.debug('Handling incoming call')\n        ringLine = lines.pop(0)\n        if self._extendedIncomingCallIndication:\n            try:\n                callType = ringLine.split(' ', 1)[1]\n            except IndexError:\n                # Some external 3G scripts modify incoming call indication settings (issue #18)\n                self.log.debug('Extended incoming call indication format changed externally; re-enabling...')\n                callType = None\n                try:\n                    # Re-enable extended format of incoming indication (optional)\n                    self.write('AT+CRC=1')\n                except CommandError:\n                    self.log.warning('Extended incoming call indication format changed externally; unable to re-enable')\n                    self._extendedIncomingCallIndication = False\n        else:\n            callType = None\n        if self._callingLineIdentification and len(lines) > 0:\n            clipLine = lines.pop(0)\n            clipMatch = self.CLIP_REGEX.match(clipLine)\n            if clipMatch:\n                callerNumber = '+' + clipMatch.group(1)\n                ton = clipMatch.group(2)\n                #TODO: re-add support for this\n                callerName = None\n                #callerName = clipMatch.group(3)\n                #if callerName != None and len(callerName) == 0:\n                #    callerName = None\n            else:\n                callerNumber = ton = callerName = None\n        else:\n            callerNumber = ton = callerName = None\n\n        call = None\n        for activeCall in dictValuesIter(self.activeCalls):\n            if activeCall.number == callerNumber:\n                call = activeCall\n                call.ringCount += 1\n        if call == None:\n            callId = len(self.activeCalls) + 1;\n            call = IncomingCall(self, callerNumber, ton, callerName, callId, callType)\n            self.activeCalls[callId] = call\n        self.incomingCallCallback(call)\n\n    def _handleCallInitiated(self, regexMatch, callId=None, callType=1):\n        \"\"\" Handler for \"outgoing call initiated\" event notification line \"\"\"\n        if self._dialEvent:\n            if regexMatch:\n                groups = regexMatch.groups()\n                # Set self._dialReponse to (callId, callType)\n                if len(groups) >= 2:\n                    self._dialResponse = (int(groups[0]) , int(groups[1]))\n                else:\n                    self._dialResponse = (int(groups[0]), 1) # assume call type: VOICE\n            else:\n                self._dialResponse = callId, callType\n            self._dialEvent.set()\n\n    def _handleCallAnswered(self, regexMatch, callId=None):\n        \"\"\" Handler for \"outgoing call answered\" event notification line \"\"\"\n        if regexMatch:\n            groups = regexMatch.groups()\n            if len(groups) > 1:\n                callId = int(groups[0])\n                self.activeCalls[callId].answered = True\n            else:\n                # Call ID not available for this notificition - check for the first outgoing call that has not been answered\n                for call in dictValuesIter(self.activeCalls):\n                    if call.answered == False and type(call) == Call:\n                        call.answered = True\n                        return\n        else:\n            # Use supplied values\n            self.activeCalls[callId].answered = True\n\n    def _handleCallEnded(self, regexMatch, callId=None, filterUnanswered=False):\n        if regexMatch:\n            groups = regexMatch.groups()\n            if len(groups) > 0:\n                callId = int(groups[0])\n            else:\n                # Call ID not available for this notification - check for the first outgoing call that is active\n                for call in dictValuesIter(self.activeCalls):\n                    if type(call) == Call:\n                        if not filterUnanswered or (filterUnanswered == True and call.answered == False):\n                            callId = call.id\n                            break\n        if callId and callId in self.activeCalls:\n            self.activeCalls[callId].answered = False\n            self.activeCalls[callId].active = False\n            del self.activeCalls[callId]\n\n    def _handleCallRejected(self, regexMatch, callId=None):\n        \"\"\" Handler for rejected (unanswered calls being ended)\n\n        Most modems use _handleCallEnded for handling both call rejections and remote hangups.\n        This method does the same, but filters for unanswered calls only.\n        \"\"\"\n        return self._handleCallEnded(regexMatch, callId, True)\n\n    def _handleSmsReceived(self, notificationLine):\n        \"\"\" Handler for \"new SMS\" unsolicited notification line \"\"\"\n        self.log.debug('SMS message received')\n        if self.smsReceivedCallback is not None:\n            cmtiMatch = self.CMTI_REGEX.match(notificationLine)\n            if cmtiMatch:\n                msgMemory = cmtiMatch.group(1)\n                msgIndex = cmtiMatch.group(2)\n                sms = self.readStoredSms(msgIndex, msgMemory)\n                try:\n                    self.smsReceivedCallback(sms)\n                except Exception:\n                    self.log.error('error in smsReceivedCallback', exc_info=True)\n                else:\n                    self.deleteStoredSms(msgIndex)\n\n    def _handleSmsStatusReport(self, notificationLine):\n        \"\"\" Handler for SMS status reports \"\"\"\n        self.log.debug('SMS status report received')\n        cdsiMatch = self.CDSI_REGEX.match(notificationLine)\n        if cdsiMatch:\n            msgMemory = cdsiMatch.group(1)\n            msgIndex = cdsiMatch.group(2)\n            report = self.readStoredSms(msgIndex, msgMemory)\n            self.deleteStoredSms(msgIndex)\n            # Update sent SMS status if possible\n            if report.reference in self.sentSms:\n                self.sentSms[report.reference].report = report\n            if self._smsStatusReportEvent:\n                # A sendSms() call is waiting for this response - notify waiting thread\n                self._smsStatusReportEvent.set()\n            elif self.smsStatusReportCallback:\n                # Nothing is waiting for this report directly - use callback\n                try:\n                    self.smsStatusReportCallback(report)\n                except Exception:\n                    self.log.error('error in smsStatusReportCallback', exc_info=True)\n\n    def _handleSmsStatusReportTe(self, length, notificationLine):\n        \"\"\" Handler for TE SMS status reports \"\"\"\n        self.log.debug('TE SMS status report received')\n        try:\n            smsDict = decodeSmsPdu(notificationLine)\n        except EncodingError:\n            self.log.debug('Discarding notification line from +CDS response: %s', notificationLine)\n        else:\n            if smsDict['type'] == 'SMS-STATUS-REPORT':\n                report = StatusReport(self, int(smsDict['status']), smsDict['reference'], smsDict['number'], smsDict['time'], smsDict['discharge'], smsDict['status'])\n            else:\n                raise CommandError('Invalid PDU type for readStoredSms(): {0}'.format(smsDict['type']))\n        # Update sent SMS status if possible\n        if report.reference in self.sentSms:\n            self.sentSms[report.reference].report = report\n        if self._smsStatusReportEvent:\n            # A sendSms() call is waiting for this response - notify waiting thread\n            self._smsStatusReportEvent.set()\n        else:\n            # Nothing is waiting for this report directly - use callback\n            try:\n                self.smsStatusReportCallback(report)\n            except Exception:\n                self.log.error('error in smsStatusReportCallback', exc_info=True)\n\n    def readStoredSms(self, index, memory=None):\n        \"\"\" Reads and returns the SMS message at the specified index\n\n        :param index: The index of the SMS message in the specified memory\n        :type index: int\n        :param memory: The memory type to read from. If None, use the current default SMS read memory\n        :type memory: str or None\n\n        :raise CommandError: if unable to read the stored message\n\n        :return: The SMS message\n        :rtype: subclass of gsmmodem.modem.Sms (either ReceivedSms or StatusReport)\n        \"\"\"\n        # Switch to the correct memory type if required\n        self._setSmsMemory(readDelete=memory)\n        msgData = self.write('AT+CMGR={0}'.format(index))\n        # Parse meta information\n        if self.smsTextMode:\n            cmgrMatch = self.CMGR_SM_DELIVER_REGEX_TEXT.match(msgData[0])\n            if cmgrMatch:\n                msgStatus, number, msgTime = cmgrMatch.groups()\n                msgText = '\\n'.join(msgData[1:-1])\n                return ReceivedSms(self, Sms.TEXT_MODE_STATUS_MAP[msgStatus], number, parseTextModeTimeStr(msgTime), msgText)\n            else:\n                # Try parsing status report\n                cmgrMatch = self.CMGR_SM_REPORT_REGEXT_TEXT.match(msgData[0])\n                if cmgrMatch:\n                    msgStatus, reference, number, sentTime, deliverTime, deliverStatus = cmgrMatch.groups()\n                    if msgStatus.startswith('\"'):\n                        msgStatus = msgStatus[1:-1]\n                    if len(msgStatus) == 0:\n                        msgStatus = \"REC UNREAD\"\n                    return StatusReport(self, Sms.TEXT_MODE_STATUS_MAP[msgStatus], int(reference), number, parseTextModeTimeStr(sentTime), parseTextModeTimeStr(deliverTime), int(deliverStatus))\n                else:\n                    raise CommandError('Failed to parse text-mode SMS message +CMGR response: {0}'.format(msgData))\n        else:\n            cmgrMatch = self.CMGR_REGEX_PDU.match(msgData[0])\n            if not cmgrMatch:\n                raise CommandError('Failed to parse PDU-mode SMS message +CMGR response: {0}'.format(msgData))\n            stat, alpha, length = cmgrMatch.groups()\n            try:\n                stat = int(stat)\n            except Exception:\n                # Some modems (ZTE) do not always read return status - default to RECEIVED UNREAD\n                stat = Sms.STATUS_RECEIVED_UNREAD\n            pdu = msgData[1]\n            smsDict = decodeSmsPdu(pdu)\n            if smsDict['type'] == 'SMS-DELIVER':\n                return ReceivedSms(self, int(stat), smsDict['number'], smsDict['time'], smsDict['text'], smsDict['smsc'], smsDict.get('udh', []))\n            elif smsDict['type'] == 'SMS-STATUS-REPORT':\n                return StatusReport(self, int(stat), smsDict['reference'], smsDict['number'], smsDict['time'], smsDict['discharge'], smsDict['status'])\n            else:\n                raise CommandError('Invalid PDU type for readStoredSms(): {0}'.format(smsDict['type']))\n\n    def deleteStoredSms(self, index, memory=None):\n        \"\"\" Deletes the SMS message stored at the specified index in modem/SIM card memory\n\n        :param index: The index of the SMS message in the specified memory\n        :type index: int\n        :param memory: The memory type to delete from. If None, use the current default SMS read/delete memory\n        :type memory: str or None\n\n        :raise CommandError: if unable to delete the stored message\n        \"\"\"\n        self._setSmsMemory(readDelete=memory)\n        self.write('AT+CMGD={0},0'.format(index))\n        # TODO: make a check how many params are supported by the modem and use the right command. For example, Siemens MC35, TC35 take only one parameter.\n        #self.write('AT+CMGD={0}'.format(index))\n\n    def deleteMultipleStoredSms(self, delFlag=4, memory=None):\n        \"\"\" Deletes all SMS messages that have the specified read status.\n\n        The messages are read from the memory set by the \"memory\" parameter.\n        The value of the \"delFlag\" paramater is the same as the \"DelFlag\" parameter of the +CMGD command:\n        1: Delete All READ messages\n        2: Delete All READ and SENT messages\n        3: Delete All READ, SENT and UNSENT messages\n        4: Delete All messages (this is the default)\n\n        :param delFlag: Controls what type of messages to delete; see description above.\n        :type delFlag: int\n        :param memory: The memory type to delete from. If None, use the current default SMS read/delete memory\n        :type memory: str or None\n        :param delete: If True, delete returned messages from the device/SIM card\n        :type delete: bool\n\n        :raise ValueErrror: if \"delFlag\" is not in range [1,4]\n        :raise CommandError: if unable to delete the stored messages\n        \"\"\"\n        if 0 < delFlag <= 4:\n            self._setSmsMemory(readDelete=memory)\n            self.write('AT+CMGD=1,{0}'.format(delFlag))\n        else:\n            raise ValueError('\"delFlag\" must be in range [1,4]')\n\n    def _handleUssd(self, lines):\n        \"\"\" Handler for USSD event notification line(s) \"\"\"\n        if self._ussdSessionEvent:\n            # A sendUssd() call is waiting for this response - parse it\n            self._ussdResponse = self._parseCusdResponse(lines)\n            # Notify waiting thread\n            self._ussdSessionEvent.set()\n\n    def _parseCusdResponse(self, lines):\n        \"\"\" Parses one or more +CUSD notification lines (for USSD)\n        :return: USSD response object\n        :rtype: gsmmodem.modem.Ussd\n        \"\"\"\n        if len(lines) > 1:\n            # Issue #20: Some modem/network combinations use \\r\\n as in-message EOL indicators;\n            # - join lines to compensate for that (thanks to davidjb for the fix)\n            # Also, look for more than one +CUSD response because of certain modems' strange behaviour\n            cusdMatches = list(self.CUSD_REGEX.finditer('\\r\\n'.join(lines)))\n        else:\n            # Single standard +CUSD response\n            cusdMatches = [self.CUSD_REGEX.match(lines[0])]\n        message = None\n        sessionActive = True\n        if len(cusdMatches) > 1:\n            self.log.debug('Multiple +CUSD responses received; filtering...')\n            # Some modems issue a non-standard \"extra\" +CUSD notification for releasing the session\n            for cusdMatch in cusdMatches:\n                if cusdMatch.group(1) == '2':\n                    # Set the session to inactive, but ignore the message\n                    self.log.debug('Ignoring \"session release\" message: %s', cusdMatch.group(2))\n                    sessionActive = False\n                else:\n                    # Not a \"session release\" message\n                    message = cusdMatch.group(2)\n                    if sessionActive and cusdMatch.group(1) != '1':\n                        sessionActive = False\n        else:\n            sessionActive = cusdMatches[0].group(1) == '1'\n            message = cusdMatches[0].group(2)\n        return Ussd(self, sessionActive, message)\n\n    def _placeHolderCallback(self, *args):\n        \"\"\" Does nothing \"\"\"\n        self.log.debug('called with args: {0}'.format(args))\n\n    def _pollCallStatus(self, expectedState, callId=None, timeout=None):\n        \"\"\" Poll the status of outgoing calls.\n        This is used for modems that do not have a known set of call status update notifications.\n\n        :param expectedState: The internal state we are waiting for. 0 == initiated, 1 == answered, 2 = hangup\n        :type expectedState: int\n\n        :raise TimeoutException: If a timeout was specified, and has occurred\n        \"\"\"\n        callDone = False\n        timeLeft = timeout or 999999\n        while self.alive and not callDone and timeLeft > 0:\n            time.sleep(0.5)\n            if expectedState == 0: # Only call initializing can timeout\n                timeLeft -= 0.5\n            try:\n                clcc = self._pollCallStatusRegex.match(self.write('AT+CLCC')[0])\n            except TimeoutException as timeout:\n                # Can happend if the call was ended during our time.sleep() call\n                clcc = None\n            if clcc:\n                direction = int(clcc.group(2))\n                if direction == 0: # Outgoing call\n                    # Determine call state\n                    stat = int(clcc.group(3))\n                    if expectedState == 0: # waiting for call initiated\n                        if stat == 2 or stat == 3: # Dialing or ringing (\"alerting\")\n                            callId = int(clcc.group(1))\n                            callType = int(clcc.group(4))\n                            self._handleCallInitiated(None, callId, callType) # if self_dialEvent is None, this does nothing\n                            expectedState = 1 # Now wait for call answer\n                    elif expectedState == 1: # waiting for call to be answered\n                        if stat == 0: # Call active\n                            callId = int(clcc.group(1))\n                            self._handleCallAnswered(None, callId)\n                            expectedState = 2 # Now wait for call hangup\n            elif expectedState == 2 : # waiting for remote hangup\n                # Since there was no +CLCC response, the call is no longer active\n                callDone = True\n                self._handleCallEnded(None, callId=callId)\n            elif expectedState == 1: # waiting for call to be answered\n                # Call was rejected\n                callDone = True\n                self._handleCallRejected(None, callId=callId)\n        if timeLeft <= 0:\n            raise TimeoutException()\n\n\nclass Call(object):\n    \"\"\" A voice call \"\"\"\n\n    DTMF_COMMAND_BASE = '+VTS='\n    dtmfSupport = False # Indicates whether or not DTMF tones can be sent in calls\n\n    def __init__(self, gsmModem, callId, callType, number, callStatusUpdateCallbackFunc=None):\n        \"\"\"\n        :param gsmModem: GsmModem instance that created this object\n        :param number: The number that is being called\n        \"\"\"\n        self._gsmModem = weakref.proxy(gsmModem)\n        self._callStatusUpdateCallbackFunc = callStatusUpdateCallbackFunc\n        # Unique ID of this call\n        self.id = callId\n        # Call type (VOICE == 0, etc)\n        self.type = callType\n        # The remote number of this call (destination or origin)\n        self.number = number\n        # Flag indicating whether the call has been answered or not (backing field for \"answered\" property)\n        self._answered = False\n        # Flag indicating whether or not the call is active\n        # (meaning it may be ringing or answered, but not ended because of a hangup event)\n        self.active = True\n\n    @property\n    def answered(self):\n        return self._answered\n    @answered.setter\n    def answered(self, answered):\n        self._answered = answered\n        if self._callStatusUpdateCallbackFunc:\n            self._callStatusUpdateCallbackFunc(self)\n\n    def sendDtmfTone(self, tones):\n        \"\"\" Send one or more DTMF tones to the remote party (only allowed for an answered call)\n\n        Note: this is highly device-dependent, and might not work\n\n        :param digits: A str containining one or more DTMF tones to play, e.g. \"3\" or \"\\*123#\"\n\n        :raise CommandError: if the command failed/is not supported\n        :raise InvalidStateException: if the call has not been answered, or is ended while the command is still executing\n        \"\"\"\n        if self.answered:\n            dtmfCommandBase = self.DTMF_COMMAND_BASE.format(cid=self.id)\n            toneLen = len(tones)\n            for tone in list(tones):\n              try:\n                 self._gsmModem.write('AT{0}{1}'.format(dtmfCommandBase,tone), timeout=(5 + toneLen))\n\n              except CmeError as e:\n                if e.code == 30:\n                    # No network service - can happen if call is ended during DTMF transmission (but also if DTMF is sent immediately after call is answered)\n                    raise InterruptedException('No network service', e)\n                elif e.code == 3:\n                    # Operation not allowed - can happen if call is ended during DTMF transmission\n                    raise InterruptedException('Operation not allowed', e)\n                else:\n                    raise e\n        else:\n            raise InvalidStateException('Call is not active (it has not yet been answered, or it has ended).')\n\n    def hangup(self):\n        \"\"\" End the phone call.\n\n        Does nothing if the call is already inactive.\n        \"\"\"\n        if self.active:\n            self._gsmModem.write('ATH')\n            self.answered = False\n            self.active = False\n        if self.id in self._gsmModem.activeCalls:\n            del self._gsmModem.activeCalls[self.id]\n\n\nclass IncomingCall(Call):\n\n    CALL_TYPE_MAP = {'VOICE': 0}\n\n    \"\"\" Represents an incoming call, conveniently allowing access to call meta information and -control \"\"\"\n    def __init__(self, gsmModem, number, ton, callerName, callId, callType):\n        \"\"\"\n        :param gsmModem: GsmModem instance that created this object\n        :param number: Caller number\n        :param ton: TON (type of number/address) in integer format\n        :param callType: Type of the incoming call (VOICE, FAX, DATA, etc)\n        \"\"\"\n        if callType in self.CALL_TYPE_MAP:\n            callType = self.CALL_TYPE_MAP[callType]\n        super(IncomingCall, self).__init__(gsmModem, callId, callType, number)\n        # Type attribute of the incoming call\n        self.ton = ton\n        self.callerName = callerName\n        # Flag indicating whether the call is ringing or not\n        self.ringing = True\n        # Amount of times this call has rung (before answer/hangup)\n        self.ringCount = 1\n\n    def answer(self):\n        \"\"\" Answer the phone call.\n        :return: self (for chaining method calls)\n        \"\"\"\n        if self.ringing:\n            self._gsmModem.write('ATA')\n            self.ringing = False\n            self.answered = True\n        return self\n\n    def hangup(self):\n        \"\"\" End the phone call. \"\"\"\n        self.ringing = False\n        super(IncomingCall, self).hangup()\n\nclass Ussd(object):\n    \"\"\" Unstructured Supplementary Service Data (USSD) message.\n\n    This class contains convenient methods for replying to a USSD prompt\n    and to cancel the USSD session\n    \"\"\"\n\n    def __init__(self, gsmModem, sessionActive, message):\n        self._gsmModem = weakref.proxy(gsmModem)\n        # Indicates if the session is active (True) or has been closed (False)\n        self.sessionActive = sessionActive\n        self.message = message\n\n    def reply(self, message):\n        \"\"\" Sends a reply to this USSD message in the same USSD session\n\n        :raise InvalidStateException: if the USSD session is not active (i.e. it has ended)\n\n        :return: The USSD response message/session (as a Ussd object)\n        \"\"\"\n        if self.sessionActive:\n            return self._gsmModem.sendUssd(message)\n        else:\n            raise InvalidStateException('USSD session is inactive')\n\n    def cancel(self):\n        \"\"\" Terminates/cancels the USSD session (without sending a reply)\n\n        Does nothing if the USSD session is inactive.\n        \"\"\"\n        if self.sessionActive:\n            self._gsmModem.write('AT+CUSD=2')\n"
  },
  {
    "path": "gsmmodem/pdu.py",
    "content": "# -*- coding: utf8 -*-\n\n\"\"\" SMS PDU encoding methods \"\"\"\n\nfrom __future__ import unicode_literals\n\nimport sys, codecs\nfrom datetime import datetime, timedelta, tzinfo\nfrom copy import copy\nfrom .exceptions import EncodingError\n\n# For Python 3 support\nPYTHON_VERSION = sys.version_info[0]\nif PYTHON_VERSION >= 3:\n    MAX_INT = sys.maxsize\n    dictItemsIter = dict.items\n    xrange = range\n    unichr = chr\n    toByteArray = lambda x: bytearray(codecs.decode(x, 'hex_codec')) if type(x) == bytes else bytearray(codecs.decode(bytes(x, 'ascii'), 'hex_codec')) if type(x)  == str else x\n    rawStrToByteArray = lambda x: bytearray(bytes(x, 'latin-1'))\nelse: #pragma: no cover\n    MAX_INT = sys.maxint\n    dictItemsIter = dict.iteritems\n    toByteArray = lambda x: bytearray(x.decode('hex')) if type(x) in (str, unicode) else x\n    rawStrToByteArray = bytearray\n\nTEXT_MODE = ('\\n\\r !\\\"#%&\\'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') # TODO: Check if all of them are supported inside text mode\n# Tables can be found at: http://en.wikipedia.org/wiki/GSM_03.38#GSM_7_bit_default_alphabet_and_extension_table_of_3GPP_TS_23.038_.2F_GSM_03.38\nGSM7_BASIC = ('@£$¥èéùìòÇ\\nØø\\rÅåΔ_ΦΓΛΩΠΨΣΘΞ\\x1bÆæßÉ !\\\"#¤%&\\'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑÜ`¿abcdefghijklmnopqrstuvwxyzäöñüà')\nGSM7_EXTENDED = {chr(0xFF): 0x0A,\n                 #CR2: chr(0x0D),\n                 '^':  chr(0x14),\n                 #SS2: chr(0x1B),\n                 '{':  chr(0x28),\n                 '}':  chr(0x29),\n                 '\\\\': chr(0x2F),\n                 '[':  chr(0x3C),\n                 '~':  chr(0x3D),\n                 ']':  chr(0x3E),\n                 '|':  chr(0x40),\n                 '€':  chr(0x65)}\n# Maximum message sizes for each data coding\nMAX_MESSAGE_LENGTH = {0x00: 160, # GSM-7\n                      0x04: 140, # 8-bit\n                      0x08: 70}  # UCS2\n\n# Maximum message sizes for each data coding for multipart messages\nMAX_MULTIPART_MESSAGE_LENGTH = {0x00: 153, # GSM-7\n                                0x04: 133, # 8-bit TODO: Check this value!\n                                0x08: 67}  # UCS2\n\nclass SmsPduTzInfo(tzinfo):\n    \"\"\" Simple implementation of datetime.tzinfo for handling timestamp GMT offsets specified in SMS PDUs \"\"\"\n\n    def __init__(self, pduOffsetStr=None):\n        \"\"\"\n        :param pduOffset: 2 semi-octet timezone offset as specified by PDU (see GSM 03.40 spec)\n        :type pduOffset: str\n\n        Note: pduOffsetStr is optional in this constructor due to the special requirement for pickling\n        mentioned in the Python docs. It should, however, be used (or otherwise pduOffsetStr must be\n        manually set)\n        \"\"\"\n        self._offset = None\n        if pduOffsetStr != None:\n            self._setPduOffsetStr(pduOffsetStr)\n\n    def _setPduOffsetStr(self, pduOffsetStr):\n        # See if the timezone difference is positive/negative by checking MSB of first semi-octet\n        tzHexVal = int(pduOffsetStr, 16)\n        # In order to read time zone 'minute' shift:\n        #  - Remove MSB (sign)\n        #  - Read HEX value as decimal\n        #  - Multiply by 15\n        # See: https://en.wikipedia.org/wiki/GSM_03.40#Time_Format\n\n        # Possible fix for #15 - convert invalid character to BCD-value\n        if (tzHexVal & 0x0F) > 0x9:\n            tzHexVal +=0x06\n\n        tzOffsetMinutes = int('{0:0>2X}'.format(tzHexVal & 0x7F)) * 15\n\n        if tzHexVal & 0x80 == 0: # positive\n            self._offset = timedelta(minutes=(tzOffsetMinutes))\n        else: # negative\n            self._offset = timedelta(minutes=(-tzOffsetMinutes))\n\n    def utcoffset(self, dt):\n        return self._offset\n\n    def dst(self, dt):\n        \"\"\" We do not have enough info in the SMS PDU to implement daylight savings time \"\"\"\n        return timedelta(0)\n\n\nclass InformationElement(object):\n    \"\"\" User Data Header (UDH) Information Element (IE) implementation\n\n    This represents a single field (\"information element\") in the PDU's\n    User Data Header. The UDH itself contains one or more of these\n    information elements.\n\n    If the IEI (IE identifier) is recognized, the class will automatically\n    specialize into one of the subclasses of InformationElement,\n    e.g. Concatenation or PortAddress, allowing the user to easily\n    access the specific (and useful) attributes of these special cases.\n    \"\"\"\n\n    def __new__(cls, *args, **kwargs): #iei, ieLen, ieData):\n        \"\"\" Causes a new InformationElement class, or subclass\n        thereof, to be created. If the IEI is recognized, a specific\n        subclass of InformationElement is returned \"\"\"\n        if len(args) > 0:\n            targetClass = IEI_CLASS_MAP.get(args[0], cls)\n        elif 'iei' in kwargs:\n            targetClass = IEI_CLASS_MAP.get(kwargs['iei'], cls)\n        else:\n            return super(InformationElement, cls).__new__(cls)\n        return super(InformationElement, targetClass).__new__(targetClass)\n\n    def __init__(self, iei, ieLen=0, ieData=None):\n        self.id = iei # IEI\n        self.dataLength = ieLen # IE Length\n        self.data = ieData or [] # raw IE data\n\n    @classmethod\n    def decode(cls, byteIter):\n        \"\"\" Decodes a single IE at the current position in the specified\n        byte iterator\n\n        :return: An InformationElement (or subclass) instance for the decoded IE\n        :rtype: InformationElement, or subclass thereof\n        \"\"\"\n        iei = next(byteIter)\n        ieLen = next(byteIter)\n        ieData = []\n        for i in xrange(ieLen):\n            ieData.append(next(byteIter))\n        return InformationElement(iei, ieLen, ieData)\n\n    def encode(self):\n        \"\"\" Encodes this IE and returns the resulting bytes \"\"\"\n        result = bytearray()\n        result.append(self.id)\n        result.append(self.dataLength)\n        result.extend(self.data)\n        return result\n\n    def __len__(self):\n        \"\"\" Exposes the IE's total length (including the IEI and IE length octet) in octets \"\"\"\n        return self.dataLength + 2\n\n\nclass Concatenation(InformationElement):\n    \"\"\" IE that indicates SMS concatenation.\n\n    This implementation handles both 8-bit and 16-bit concatenation\n    indication, and exposes the specific useful details of this\n    IE as instance variables.\n\n    Exposes:\n\n    reference\n        CSMS reference number, must be same for all the SMS parts in the CSMS\n    parts\n        total number of parts. The value shall remain constant for every short\n        message which makes up the concatenated short message. If the value is zero then\n        the receiving entity shall ignore the whole information element\n    number\n        this part's number in the sequence. The value shall start at 1 and\n        increment for every short message which makes up the concatenated short message\n    \"\"\"\n\n    def __init__(self, iei=0x00, ieLen=0, ieData=None):\n        super(Concatenation, self).__init__(iei, ieLen, ieData)\n        if ieData != None:\n            if iei == 0x00: # 8-bit reference\n                self.reference, self.parts, self.number = ieData\n            else: # 0x08: 16-bit reference\n                self.reference = ieData[0] << 8 | ieData[1]\n                self.parts = ieData[2]\n                self.number = ieData[3]\n\n    def encode(self):\n        if self.reference > 0xFF:\n            self.id = 0x08 # 16-bit reference\n            self.data = [self.reference >> 8, self.reference & 0xFF, self.parts, self.number]\n        else:\n            self.id = 0x00 # 8-bit reference\n            self.data = [self.reference, self.parts, self.number]\n        self.dataLength = len(self.data)\n        return super(Concatenation, self).encode()\n\n\nclass PortAddress(InformationElement):\n    \"\"\" IE that indicates an Application Port Addressing Scheme.\n\n    This implementation handles both 8-bit and 16-bit concatenation\n    indication, and exposes the specific useful details of this\n    IE as instance variables.\n\n    Exposes:\n    destination: The destination port number\n    source: The source port number\n    \"\"\"\n\n    def __init__(self, iei=0x04, ieLen=0, ieData=None):\n        super(PortAddress, self).__init__(iei, ieLen, ieData)\n        if ieData != None:\n            if iei == 0x04: # 8-bit port addressing scheme\n                self.destination, self.source = ieData\n            else: # 0x05: 16-bit port addressing scheme\n                self.destination = ieData[0] << 8 | ieData[1]\n                self.source = ieData[2] << 8 | ieData[3]\n\n    def encode(self):\n        if self.destination > 0xFF or self.source > 0xFF:\n            self.id = 0x05 # 16-bit\n            self.data = [self.destination >> 8, self.destination & 0xFF, self.source >> 8, self.source & 0xFF]\n        else:\n            self.id = 0x04 # 8-bit\n            self.data = [self.destination, self.source]\n        self.dataLength = len(self.data)\n        return super(PortAddress, self).encode()\n\n\n# Map of recognized IEIs\nIEI_CLASS_MAP = {0x00: Concatenation, # Concatenated short messages, 8-bit reference number\n                 0x08: Concatenation, # Concatenated short messages, 16-bit reference number\n                 0x04: PortAddress, # Application port addressing scheme, 8 bit address\n                 0x05: PortAddress # Application port addressing scheme, 16 bit address\n                }\n\n\nclass Pdu(object):\n    \"\"\" Encoded SMS PDU. Contains raw PDU data and related meta-information \"\"\"\n\n    def __init__(self, data, tpduLength):\n        \"\"\" Constructor\n        :param data: the raw PDU data (as bytes)\n        :type data: bytearray\n        :param tpduLength: Length (in bytes) of the TPDU\n        :type tpduLength: int\n        \"\"\"\n        self.data = data\n        self.tpduLength = tpduLength\n\n    def __str__(self):\n        global PYTHON_VERSION\n        if PYTHON_VERSION < 3:\n            return str(self.data).encode('hex').upper()\n        else: #pragma: no cover\n            return str(codecs.encode(self.data, 'hex_codec'), 'ascii').upper()\n\n\ndef encodeSmsSubmitPdu(number, text, reference=0, validity=None, smsc=None, requestStatusReport=True, rejectDuplicates=False, sendFlash=False):\n    \"\"\" Creates an SMS-SUBMIT PDU for sending a message with the specified text to the specified number\n\n    :param number: the destination mobile number\n    :type number: str\n    :param text: the message text\n    :type text: str\n    :param reference: message reference number (see also: rejectDuplicates parameter)\n    :type reference: int\n    :param validity: message validity period (absolute or relative)\n    :type validity: datetime.timedelta (relative) or datetime.datetime (absolute)\n    :param smsc: SMSC number to use (leave None to use default)\n    :type smsc: str\n    :param rejectDuplicates: Flag that controls the TP-RD parameter (messages with same destination and reference may be rejected if True)\n    :type rejectDuplicates: bool\n\n    :return: A list of one or more tuples containing the SMS PDU (as a bytearray, and the length of the TPDU part\n    :rtype: list of tuples\n    \"\"\"\n    if PYTHON_VERSION < 3:\n        if type(text) == str:\n            text = text.decode('UTF-8')\n\n    tpduFirstOctet = 0x01 # SMS-SUBMIT PDU\n    if validity != None:\n        # Validity period format (TP-VPF) is stored in bits 4,3 of the first TPDU octet\n        if type(validity) == timedelta:\n            # Relative (TP-VP is integer)\n            tpduFirstOctet |= 0x10 # bit4 == 1, bit3 == 0\n            validityPeriod = [_encodeRelativeValidityPeriod(validity)]\n        elif type(validity) == datetime:\n            # Absolute (TP-VP is semi-octet encoded date)\n            tpduFirstOctet |= 0x18 # bit4 == 1, bit3 == 1\n            validityPeriod = _encodeTimestamp(validity)\n        else:\n            raise TypeError('\"validity\" must be of type datetime.timedelta (for relative value) or datetime.datetime (for absolute value)')\n    else:\n        validityPeriod = None\n    if rejectDuplicates:\n        tpduFirstOctet |= 0x04 # bit2 == 1\n    if requestStatusReport:\n        tpduFirstOctet |= 0x20 # bit5 == 1\n\n    # Encode message text and set data coding scheme based on text contents\n    try:\n        encodedTextLength = len(encodeGsm7(text))\n    except ValueError:\n        # Cannot encode text using GSM-7; use UCS2 instead\n        encodedTextLength = len(text)\n        alphabet = 0x08 # UCS2\n    else:\n        alphabet = 0x00 # GSM-7\n\n    # Check if message should be concatenated\n    if encodedTextLength > MAX_MESSAGE_LENGTH[alphabet]:\n        # Text too long for single PDU - add \"concatenation\" User Data Header\n        concatHeaderPrototype = Concatenation()\n        concatHeaderPrototype.reference = reference\n\n        # Devide whole text into parts\n        if alphabet == 0x00:\n            pduTextParts = divideTextGsm7(text)\n        elif alphabet == 0x08:\n            pduTextParts = divideTextUcs2(text)\n        else:\n            raise NotImplementedError\n\n        pduCount = len(pduTextParts)\n        concatHeaderPrototype.parts  = pduCount\n        tpduFirstOctet |= 0x40\n    else:\n        concatHeaderPrototype = None\n        pduCount = 1\n\n    # Construct required PDU(s)\n    pdus = []\n    for i in xrange(pduCount):\n        pdu = bytearray()\n        if smsc:\n            pdu.extend(_encodeAddressField(smsc, smscField=True))\n        else:\n            pdu.append(0x00) # Don't supply an SMSC number - use the one configured in the device\n\n        udh = bytearray()\n        if concatHeaderPrototype != None:\n            concatHeader = copy(concatHeaderPrototype)\n            concatHeader.number = i + 1\n            pduText = pduTextParts[i]\n            pduTextLength = len(pduText)\n            udh.extend(concatHeader.encode())\n        else:\n            pduText = text\n\n        udhLen = len(udh)\n\n        pdu.append(tpduFirstOctet)\n        pdu.append(reference) # message reference\n        # Add destination number\n        pdu.extend(_encodeAddressField(number))\n        pdu.append(0x00) # Protocol identifier - no higher-level protocol\n\n        pdu.append(alphabet if not sendFlash else (0x10 if alphabet == 0x00 else 0x18))\n        if validityPeriod:\n            pdu.extend(validityPeriod)\n\n        if alphabet == 0x00: # GSM-7\n            encodedText = encodeGsm7(pduText)\n            userDataLength = len(encodedText) # Payload size in septets/characters\n            if udhLen > 0:\n                shift = ((udhLen + 1) * 8) % 7 # \"fill bits\" needed to make the UDH end on a septet boundary\n                userData = packSeptets(encodedText, padBits=shift)\n                if shift > 0:\n                    userDataLength += 1 # take padding bits into account\n            else:\n                userData = packSeptets(encodedText)\n        elif alphabet == 0x08: # UCS2\n            userData = encodeUcs2(pduText)\n            userDataLength = len(userData)\n\n        if udhLen > 0:\n            userDataLength += udhLen + 1 # +1 for the UDH length indicator byte\n            pdu.append(userDataLength)\n            pdu.append(udhLen)\n            pdu.extend(udh) # UDH\n        else:\n            pdu.append(userDataLength)\n        pdu.extend(userData) # User Data (message payload)\n        tpdu_length = len(pdu) - 1\n        pdus.append(Pdu(pdu, tpdu_length))\n    return pdus\n\ndef decodeSmsPdu(pdu):\n    \"\"\" Decodes SMS pdu data and returns a tuple in format (number, text)\n\n    :param pdu: PDU data as a hex string, or a bytearray containing PDU octects\n    :type pdu: str or bytearray\n\n    :raise EncodingError: If the specified PDU data cannot be decoded\n\n    :return: The decoded SMS data as a dictionary\n    :rtype: dict\n    \"\"\"\n    try:\n        pdu = toByteArray(pdu)\n    except Exception as e:\n        # Python 2 raises TypeError, Python 3 raises binascii.Error\n        raise EncodingError(e)\n    result = {}\n    pduIter = iter(pdu)\n\n    smscNumber, smscBytesRead = _decodeAddressField(pduIter, smscField=True)\n    result['smsc'] = smscNumber\n    result['tpdu_length'] = len(pdu) - smscBytesRead\n\n    tpduFirstOctet = next(pduIter)\n\n    pduType = tpduFirstOctet & 0x03 # bits 1-0\n    if pduType == 0x00: # SMS-DELIVER or SMS-DELIVER REPORT\n        result['type'] = 'SMS-DELIVER'\n        result['number'] = _decodeAddressField(pduIter)[0]\n        result['protocol_id'] = next(pduIter)\n        dataCoding = _decodeDataCoding(next(pduIter))\n        result['time'] = _decodeTimestamp(pduIter)\n        userDataLen = next(pduIter)\n        udhPresent = (tpduFirstOctet & 0x40) != 0\n        ud = _decodeUserData(pduIter, userDataLen, dataCoding, udhPresent)\n        result.update(ud)\n    elif pduType == 0x01: # SMS-SUBMIT or SMS-SUBMIT-REPORT\n        result['type'] = 'SMS-SUBMIT'\n        result['reference'] = next(pduIter) # message reference - we don't really use this\n        result['number'] = _decodeAddressField(pduIter)[0]\n        result['protocol_id'] = next(pduIter)\n        dataCoding = _decodeDataCoding(next(pduIter))\n        validityPeriodFormat = (tpduFirstOctet & 0x18) >> 3 # bits 4,3\n        if validityPeriodFormat == 0x02: # TP-VP field present and integer represented (relative)\n            result['validity'] = _decodeRelativeValidityPeriod(next(pduIter))\n        elif validityPeriodFormat == 0x03: # TP-VP field present and semi-octet represented (absolute)\n            result['validity'] = _decodeTimestamp(pduIter)\n        userDataLen = next(pduIter)\n        udhPresent = (tpduFirstOctet & 0x40) != 0\n        ud = _decodeUserData(pduIter, userDataLen, dataCoding, udhPresent)\n        result.update(ud)\n    elif pduType == 0x02: # SMS-STATUS-REPORT or SMS-COMMAND\n        result['type'] = 'SMS-STATUS-REPORT'\n        result['reference'] = next(pduIter)\n        result['number'] = _decodeAddressField(pduIter)[0]\n        result['time'] = _decodeTimestamp(pduIter)\n        result['discharge'] = _decodeTimestamp(pduIter)\n        result['status'] = next(pduIter)\n    else:\n        raise EncodingError('Unknown SMS message type: {0}. First TPDU octet was: {1}'.format(pduType, tpduFirstOctet))\n\n    return result\n\ndef _decodeUserData(byteIter, userDataLen, dataCoding, udhPresent):\n    \"\"\" Decodes PDU user data (UDHI (if present) and message text) \"\"\"\n    result = {}\n    if udhPresent:\n        # User Data Header is present\n        result['udh'] = []\n        udhLen = next(byteIter)\n        ieLenRead = 0\n        # Parse and store UDH fields\n        while ieLenRead < udhLen:\n            ie = InformationElement.decode(byteIter)\n            ieLenRead += len(ie)\n            result['udh'].append(ie)\n        del ieLenRead\n        if dataCoding == 0x00: # GSM-7\n            # Since we are using 7-bit data, \"fill bits\" may have been added to make the UDH end on a septet boundary\n            shift = ((udhLen + 1) * 8) % 7 # \"fill bits\" needed to make the UDH end on a septet boundary\n            # Simulate another \"shift\" in the unpackSeptets algorithm in order to ignore the fill bits\n            prevOctet = next(byteIter)\n            shift += 1\n\n    if dataCoding == 0x00: # GSM-7\n        if udhPresent:\n            userDataSeptets = unpackSeptets(byteIter, userDataLen, prevOctet, shift)\n        else:\n            userDataSeptets = unpackSeptets(byteIter, userDataLen)\n        result['text'] = decodeGsm7(userDataSeptets)\n    elif dataCoding == 0x02: # UCS2\n        result['text'] = decodeUcs2(byteIter, userDataLen)\n    else: # 8-bit (data)\n        userData = []\n        for b in byteIter:\n            userData.append(unichr(b))\n        result['text'] = ''.join(userData)\n    return result\n\ndef _decodeRelativeValidityPeriod(tpVp):\n    \"\"\" Calculates the relative SMS validity period (based on the table in section 9.2.3.12 of GSM 03.40)\n    :rtype: datetime.timedelta\n    \"\"\"\n    if tpVp <= 143:\n        return timedelta(minutes=((tpVp + 1) * 5))\n    elif 144 <= tpVp <= 167:\n        return timedelta(hours=12, minutes=((tpVp - 143) * 30))\n    elif 168 <= tpVp <= 196:\n        return timedelta(days=(tpVp - 166))\n    elif 197 <= tpVp <= 255:\n        return timedelta(weeks=(tpVp - 192))\n    else:\n        raise ValueError('tpVp must be in range [0, 255]')\n\ndef _encodeRelativeValidityPeriod(validityPeriod):\n    \"\"\" Encodes the specified relative validity period timedelta into an integer for use in an SMS PDU\n    (based on the table in section 9.2.3.12 of GSM 03.40)\n\n    :param validityPeriod: The validity period to encode\n    :type validityPeriod: datetime.timedelta\n    :rtype: int\n    \"\"\"\n    # Python 2.6 does not have timedelta.total_seconds(), so compute it manually\n    #seconds = validityPeriod.total_seconds()\n    seconds = validityPeriod.seconds + (validityPeriod.days * 24 * 3600)\n    if seconds <= 43200: # 12 hours\n        tpVp = int(seconds / 300) - 1 # divide by 5 minutes, subtract 1\n    elif seconds <= 86400: # 24 hours\n        tpVp = int((seconds - 43200) / 1800) + 143 # subtract 12 hours, divide by 30 minutes. add 143\n    elif validityPeriod.days <= 30: # 30 days\n        tpVp = validityPeriod.days + 166 # amount of days + 166\n    elif validityPeriod.days <= 441: # max value of tpVp is 255\n        tpVp = int(validityPeriod.days / 7) + 192 # amount of weeks + 192\n    else:\n        raise ValueError('Validity period too long; tpVp limited to 1 octet (max value: 255)')\n    return tpVp\n\ndef _decodeTimestamp(byteIter):\n    \"\"\" Decodes a 7-octet timestamp \"\"\"\n    dateStr = decodeSemiOctets(byteIter, 7)\n    timeZoneStr = dateStr[-2:]\n    return datetime.strptime(dateStr[:-2], '%y%m%d%H%M%S').replace(tzinfo=SmsPduTzInfo(timeZoneStr))\n\ndef _encodeTimestamp(timestamp):\n    \"\"\" Encodes a 7-octet timestamp from the specified date\n\n    Note: the specified timestamp must have a UTC offset set; you can use gsmmodem.util.SimpleOffsetTzInfo for simple cases\n\n    :param timestamp: The timestamp to encode\n    :type timestamp: datetime.datetime\n\n    :return: The encoded timestamp\n    :rtype: bytearray\n    \"\"\"\n    if timestamp.tzinfo == None:\n        raise ValueError('Please specify time zone information for the timestamp (e.g. by using gsmmodem.util.SimpleOffsetTzInfo)')\n\n    # See if the timezone difference is positive/negative\n    tzDelta = timestamp.utcoffset()\n    if tzDelta.days >= 0:\n        tzValStr = '{0:0>2}'.format(int(tzDelta.seconds / 60 / 15))\n    else: # negative\n        tzVal = int((tzDelta.days * -3600 * 24 - tzDelta.seconds) / 60 / 15) # calculate offset in 0.25 hours\n        # Cast as literal hex value and set MSB of first semi-octet of timezone to 1 to indicate negative value\n        tzVal = int('{0:0>2}'.format(tzVal), 16) | 0x80\n        tzValStr = '{0:0>2X}'.format(tzVal)\n\n    dateStr = timestamp.strftime('%y%m%d%H%M%S') + tzValStr\n\n    return encodeSemiOctets(dateStr)\n\ndef _decodeDataCoding(octet):\n    if octet & 0xC0 == 0:\n        #compressed = octect & 0x20\n        alphabet = (octet & 0x0C) >> 2\n        return alphabet # 0x00 == GSM-7, 0x01 == 8-bit data, 0x02 == UCS2\n    # We ignore other coding groups\n    return 0\n\ndef nibble2octet(addressLen):\n    return int((addressLen + 1) / 2)\n\ndef _decodeAddressField(byteIter, smscField=False, log=False):\n    \"\"\" Decodes the address field at the current position of the bytearray iterator\n\n    :param byteIter: Iterator over bytearray\n    :type byteIter: iter(bytearray)\n\n    :return: Tuple containing the address value and amount of bytes read (value is or None if it is empty (zero-length))\n    :rtype: tuple\n    \"\"\"\n    addressLen = next(byteIter)\n    if addressLen > 0:\n        toa = next(byteIter)\n        ton = (toa & 0x70) # bits 6,5,4 of type-of-address == type-of-number\n        if ton == 0x50:\n            # Alphanumberic number\n            addressLen = nibble2octet(addressLen)\n            septets = unpackSeptets(byteIter, addressLen)\n            addressValue = decodeGsm7(septets)\n            return (addressValue, (addressLen + 2))\n        else:\n            # ton == 0x00: Unknown (might be international, local, etc) - leave as is\n            # ton == 0x20: National number\n            if smscField:\n                addressValue = decodeSemiOctets(byteIter, addressLen-1)\n            else:\n                addressLen = nibble2octet(addressLen)\n                addressValue = decodeSemiOctets(byteIter, addressLen)\n                addressLen += 1 # for the return value, add the toa byte\n            if ton == 0x10: # International number\n                addressValue = '+' + addressValue\n            return (addressValue, (addressLen + 1))\n    else:\n        return (None, 1)\n\ndef _encodeAddressField(address, smscField=False):\n    \"\"\" Encodes the address into an address field\n\n    :param address: The address to encode (phone number or alphanumeric)\n    :type byteIter: str\n\n    :return: Encoded SMS PDU address field\n    :rtype: bytearray\n    \"\"\"\n    # First, see if this is a number or an alphanumeric string\n    toa = 0x80 | 0x00 | 0x01 # Type-of-address start | Unknown type-of-number | ISDN/tel numbering plan\n    alphaNumeric = False\n    if address.isalnum():\n        # Might just be a local number\n        if address.isdigit():\n            # Local number\n            toa |= 0x20\n        else:\n            # Alphanumeric address\n            toa |= 0x50\n            toa &= 0xFE # switch to \"unknown\" numbering plan\n            alphaNumeric = True\n    else:\n        if address[0] == '+' and address[1:].isdigit():\n            # International number\n            toa |= 0x10\n            # Remove the '+' prefix\n            address = address[1:]\n        else:\n            # Alphanumeric address\n            toa |= 0x50\n            toa &= 0xFE # switch to \"unknown\" numbering plan\n            alphaNumeric = True\n    if  alphaNumeric:\n        addressValue = packSeptets(encodeGsm7(address, False))\n        addressLen = len(addressValue) * 2\n    else:\n        addressValue = encodeSemiOctets(address)\n        if smscField:\n            addressLen = len(addressValue) + 1\n        else:\n            addressLen = len(address)\n    result = bytearray()\n    result.append(addressLen)\n    result.append(toa)\n    result.extend(addressValue)\n    return result\n\ndef encodeSemiOctets(number):\n    \"\"\" Semi-octet encoding algorithm (e.g. for phone numbers)\n\n    :return: bytearray containing the encoded octets\n    :rtype: bytearray\n    \"\"\"\n    if len(number) % 2 == 1:\n        number = number + 'F' # append the \"end\" indicator\n    octets = [int(number[i+1] + number[i], 16) for i in xrange(0, len(number), 2)]\n    return bytearray(octets)\n\ndef decodeSemiOctets(encodedNumber, numberOfOctets=None):\n    \"\"\" Semi-octet decoding algorithm(e.g. for phone numbers)\n\n    :param encodedNumber: The semi-octet-encoded telephone number (in bytearray format or hex string)\n    :type encodedNumber: bytearray, str or iter(bytearray)\n    :param numberOfOctets: The expected amount of octets after decoding (i.e. when to stop)\n    :type numberOfOctets: int\n\n    :return: decoded telephone number\n    :rtype: string\n    \"\"\"\n    number = []\n    if type(encodedNumber) in (str, bytes):\n        encodedNumber = bytearray(codecs.decode(encodedNumber, 'hex_codec'))\n    i = 0\n    for octet in encodedNumber:\n        hexVal = hex(octet)[2:].zfill(2)\n        number.append(hexVal[1])\n        if hexVal[0] != 'f':\n            number.append(hexVal[0])\n        else:\n            break\n        if numberOfOctets != None:\n            i += 1\n            if i == numberOfOctets:\n                break\n    return ''.join(number)\n\ndef encodeTextMode(plaintext):\n    \"\"\" Text mode checker\n\n    Tests whther SMS could be sent in text mode\n\n    :param text: the text string to encode\n\n    :raise ValueError: if the text string cannot be sent in text mode\n\n    :return: Passed string\n    :rtype: str\n    \"\"\"\n    if PYTHON_VERSION >= 3:\n        plaintext = str(plaintext)\n    elif type(plaintext) == str:\n        plaintext = plaintext.decode('UTF-8')\n\n    for char in plaintext:\n        idx = TEXT_MODE.find(char)\n        if idx != -1:\n            continue\n        else:\n            raise ValueError('Cannot encode char \"{0}\" inside text mode'.format(char))\n\n    if len(plaintext) > MAX_MESSAGE_LENGTH[0x00]:\n        raise ValueError('Message is too long for text mode (maximum {0} characters)'.format(MAX_MESSAGE_LENGTH[0x00]))\n\n    return plaintext\n\ndef encodeGsm7(plaintext, discardInvalid=False):\n    \"\"\" GSM-7 text encoding algorithm\n\n    Encodes the specified text string into GSM-7 octets (characters). This method does not pack\n    the characters into septets.\n\n    :param text: the text string to encode\n    :param discardInvalid: if True, characters that cannot be encoded will be silently discarded\n\n    :raise ValueError: if the text string cannot be encoded using GSM-7 encoding (unless discardInvalid == True)\n\n    :return: A bytearray containing the string encoded in GSM-7 encoding\n    :rtype: bytearray\n    \"\"\"\n    result = bytearray()\n    if PYTHON_VERSION >= 3:\n        plaintext = str(plaintext)\n    elif type(plaintext) == str:\n        plaintext = plaintext.decode('UTF-8')\n\n    for char in plaintext:\n        idx = GSM7_BASIC.find(char)\n        if idx != -1:\n            result.append(idx)\n        elif char in GSM7_EXTENDED:\n            result.append(0x1B) # ESC - switch to extended table\n            result.append(ord(GSM7_EXTENDED[char]))\n        elif not discardInvalid:\n            raise ValueError('Cannot encode char \"{0}\" using GSM-7 encoding'.format(char))\n    return result\n\ndef decodeGsm7(encodedText):\n    \"\"\" GSM-7 text decoding algorithm\n\n    Decodes the specified GSM-7-encoded string into a plaintext string.\n\n    :param encodedText: the text string to encode\n    :type encodedText: bytearray or str\n\n    :return: A string containing the decoded text\n    :rtype: str\n    \"\"\"\n    result = []\n    if type(encodedText) == str:\n        encodedText = rawStrToByteArray(encodedText) #bytearray(encodedText)\n    iterEncoded = iter(encodedText)\n    for b in iterEncoded:\n        if b == 0x1B: # ESC - switch to extended table\n            c = chr(next(iterEncoded))\n            for char, value in dictItemsIter(GSM7_EXTENDED):\n                if c == value:\n                    result.append(char)\n                    break\n        else:\n            result.append(GSM7_BASIC[b])\n    return ''.join(result)\n\ndef divideTextGsm7(plainText):\n    \"\"\" GSM7 message dividing algorithm\n\n    Divides text into list of chunks that could be stored in a single, GSM7-encoded SMS message.\n\n    :param plainText: the text string to divide\n    :type plainText: str\n\n    :return: A list of strings\n    :rtype: list of str\n    \"\"\"\n    result = []\n\n    plainStartPtr = 0\n    plainStopPtr  = 0\n    chunkByteSize = 0\n\n    if PYTHON_VERSION >= 3:\n        plainText = str(plainText)\n    while plainStopPtr < len(plainText):\n        char = plainText[plainStopPtr]\n        idx = GSM7_BASIC.find(char)\n        if idx != -1:\n            chunkByteSize = chunkByteSize + 1;\n        elif char in GSM7_EXTENDED:\n            chunkByteSize = chunkByteSize + 2;\n        else:\n            raise ValueError('Cannot encode char \"{0}\" using GSM-7 encoding'.format(char))\n\n        plainStopPtr = plainStopPtr + 1\n        if chunkByteSize > MAX_MULTIPART_MESSAGE_LENGTH[0x00]:\n            plainStopPtr = plainStopPtr - 1\n\n        if chunkByteSize >= MAX_MULTIPART_MESSAGE_LENGTH[0x00]:\n            result.append(plainText[plainStartPtr:plainStopPtr])\n            plainStartPtr = plainStopPtr\n            chunkByteSize = 0\n\n    if chunkByteSize > 0:\n        result.append(plainText[plainStartPtr:])\n\n    return result\n\ndef packSeptets(octets, padBits=0):\n    \"\"\" Packs the specified octets into septets\n\n    Typically the output of encodeGsm7 would be used as input to this function. The resulting\n    bytearray contains the original GSM-7 characters packed into septets ready for transmission.\n\n    :rtype: bytearray\n    \"\"\"\n    result = bytearray()\n    if type(octets) == str:\n        octets = iter(rawStrToByteArray(octets))\n    elif type(octets) == bytearray:\n        octets = iter(octets)\n    shift = padBits\n    if padBits == 0:\n        try:\n            prevSeptet = next(octets)\n        except StopIteration:\n            return result\n    else:\n        prevSeptet = 0x00\n    for octet in octets:\n        septet = octet & 0x7f;\n        if shift == 7:\n            # prevSeptet has already been fully added to result\n            shift = 0\n            prevSeptet = septet\n            continue\n        b = ((septet << (7 - shift)) & 0xFF) | (prevSeptet >> shift)\n        prevSeptet = septet\n        shift += 1\n        result.append(b)\n    if shift != 7:\n        # There is a bit \"left over\" from prevSeptet\n        result.append(prevSeptet >> shift)\n    return result\n\ndef unpackSeptets(septets, numberOfSeptets=None, prevOctet=None, shift=7):\n    \"\"\" Unpacks the specified septets into octets\n\n    :param septets: Iterator or iterable containing the septets packed into octets\n    :type septets: iter(bytearray), bytearray or str\n    :param numberOfSeptets: The amount of septets to unpack (or None for all remaining in \"septets\")\n    :type numberOfSeptets: int or None\n\n    :return: The septets unpacked into octets\n    :rtype: bytearray\n    \"\"\"\n    result = bytearray()\n    if type(septets) == str:\n        septets = iter(rawStrToByteArray(septets))\n    elif type(septets) == bytearray:\n        septets = iter(septets)\n    if numberOfSeptets == None:\n        numberOfSeptets = MAX_INT # Loop until StopIteration\n    if numberOfSeptets == 0:\n        return result\n    i = 0\n    for octet in septets:\n        i += 1\n        if shift == 7:\n            shift = 1\n            if prevOctet != None:\n                result.append(prevOctet >> 1)\n            if i <= numberOfSeptets:\n                result.append(octet & 0x7F)\n                prevOctet = octet\n            if i == numberOfSeptets:\n                break\n            else:\n                continue\n        b = ((octet << shift) & 0x7F) | (prevOctet >> (8 - shift))\n\n        prevOctet = octet\n        result.append(b)\n        shift += 1\n\n        if i == numberOfSeptets:\n            break\n    if shift == 7 and prevOctet:\n        b = prevOctet >> (8 - shift)\n        if b:\n            # The final septet value still needs to be unpacked\n            result.append(b)\n    return result\n\ndef decodeUcs2(byteIter, numBytes):\n    \"\"\" Decodes UCS2-encoded text from the specified byte iterator, up to a maximum of numBytes \"\"\"\n    userData = []\n    i = 0\n    try:\n        while i < numBytes:\n            userData.append(unichr((next(byteIter) << 8) | next(byteIter)))\n            i += 2\n    except StopIteration:\n        # Not enough bytes in iterator to reach numBytes; return what we have\n        pass\n    return ''.join(userData)\n\ndef encodeUcs2(text):\n    \"\"\" UCS2 text encoding algorithm\n\n    Encodes the specified text string into UCS2-encoded bytes.\n\n    :param text: the text string to encode\n\n    :return: A bytearray containing the string encoded in UCS2 encoding\n    :rtype: bytearray\n    \"\"\"\n    result = bytearray()\n\n    for b in map(ord, text):\n        result.append(b >> 8)\n        result.append(b & 0xFF)\n    return result\n\ndef divideTextUcs2(plainText):\n    \"\"\" UCS-2 message dividing algorithm\n\n    Divides text into list of chunks that could be stored in a single, UCS-2 -encoded SMS message.\n\n    :param plainText: the text string to divide\n    :type plainText: str\n\n    :return: A list of strings\n    :rtype: list of str\n    \"\"\"\n    result = []\n    resultLength = 0\n\n    fullChunksCount = int(len(plainText) / MAX_MULTIPART_MESSAGE_LENGTH[0x08])\n    for i in range(fullChunksCount):\n        result.append(plainText[i * MAX_MULTIPART_MESSAGE_LENGTH[0x08] : (i + 1) * MAX_MULTIPART_MESSAGE_LENGTH[0x08]])\n        resultLength  = resultLength + MAX_MULTIPART_MESSAGE_LENGTH[0x08]\n\n    # Add last, not fully filled chunk\n    if resultLength < len(plainText):\n        result.append(plainText[resultLength:])\n\n    return result\n"
  },
  {
    "path": "gsmmodem/serial_comms.py",
    "content": "#!/usr/bin/env python\n\n\"\"\" Low-level serial communications handling \"\"\"\n\nimport sys, threading, logging\n\nimport re\nimport serial # pyserial: http://pyserial.sourceforge.net\n\nfrom .exceptions import TimeoutException\nfrom . import compat # For Python 2.6 compatibility\n\nclass SerialComms(object):\n    \"\"\" Wraps all low-level serial communications (actual read/write operations) \"\"\"\n\n    log = logging.getLogger('gsmmodem.serial_comms.SerialComms')\n\n    # End-of-line read terminator\n    RX_EOL_SEQ = b'\\r\\n'\n    # End-of-response terminator\n    RESPONSE_TERM = re.compile('^OK|ERROR|(\\+CM[ES] ERROR: \\d+)|(COMMAND NOT SUPPORT)$')\n    # Default timeout for serial port reads (in seconds)\n    timeout = 1\n\n    def __init__(self, port, baudrate=115200, notifyCallbackFunc=None, fatalErrorCallbackFunc=None, *args, **kwargs):\n        \"\"\" Constructor\n\n        :param fatalErrorCallbackFunc: function to call if a fatal error occurs in the serial device reading thread\n        :type fatalErrorCallbackFunc: func\n        \"\"\"\n        self.alive = False\n        self.port = port\n        self.baudrate = baudrate\n\n        self._responseEvent = None # threading.Event()\n        self._expectResponseTermSeq = None # expected response terminator sequence\n        self._response = None # Buffer containing response to a written command\n        self._notification = [] # Buffer containing lines from an unsolicited notification from the modem\n        # Reentrant lock for managing concurrent write access to the underlying serial port\n        self._txLock = threading.RLock()\n\n        self.notifyCallback = notifyCallbackFunc or self._placeholderCallback\n        self.fatalErrorCallback = fatalErrorCallbackFunc or self._placeholderCallback\n\n        self.com_args = args\n        self.com_kwargs = kwargs\n\n    def connect(self):\n        \"\"\" Connects to the device and starts the read thread \"\"\"\n        self.serial = serial.Serial(dsrdtr=True, rtscts=True, port=self.port, baudrate=self.baudrate,\n                                    timeout=self.timeout,*self.com_args,**self.com_kwargs)\n        # Start read thread\n        self.alive = True\n        self.rxThread = threading.Thread(target=self._readLoop)\n        self.rxThread.daemon = True\n        self.rxThread.start()\n\n    def close(self):\n        \"\"\" Stops the read thread, waits for it to exit cleanly, then closes the underlying serial port \"\"\"\n        self.alive = False\n        self.rxThread.join()\n        self.serial.close()\n\n    def _handleLineRead(self, line, checkForResponseTerm=True):\n        #print 'sc.hlineread:',line\n        if self._responseEvent and not self._responseEvent.is_set():\n            # A response event has been set up (another thread is waiting for this response)\n            self._response.append(line)\n            if not checkForResponseTerm or self.RESPONSE_TERM.match(line):\n                # End of response reached; notify waiting thread\n                #print 'response:', self._response\n                self.log.debug('response: %s', self._response)\n                self._responseEvent.set()\n        else:\n            # Nothing was waiting for this - treat it as a notification\n            self._notification.append(line)\n            if self.serial.inWaiting() == 0:\n                # No more chars on the way for this notification - notify higher-level callback\n                #print 'notification:', self._notification\n                self.log.debug('notification: %s', self._notification)\n                self.notifyCallback(self._notification)\n                self._notification = []\n\n    def _placeholderCallback(self, *args, **kwargs):\n        \"\"\" Placeholder callback function (does nothing) \"\"\"\n\n    def _readLoop(self):\n        \"\"\" Read thread main loop\n\n        Reads lines from the connected device\n        \"\"\"\n        try:\n            readTermSeq = bytearray(self.RX_EOL_SEQ)\n            readTermLen = len(readTermSeq)\n            rxBuffer = bytearray()\n            while self.alive:\n                data = self.serial.read(1)\n                if len(data) != 0: # check for timeout\n                    #print >> sys.stderr, ' RX:', data,'({0})'.format(ord(data))\n                    rxBuffer.append(ord(data))\n                    if rxBuffer[-readTermLen:] == readTermSeq:\n                        # A line (or other logical segment) has been read\n                        line = rxBuffer[:-readTermLen].decode()\n                        rxBuffer = bytearray()\n                        if len(line) > 0:\n                            #print 'calling handler'\n                            self._handleLineRead(line)\n                    elif self._expectResponseTermSeq:\n                        if rxBuffer[-len(self._expectResponseTermSeq):] == self._expectResponseTermSeq:\n                            line = rxBuffer.decode()\n                            rxBuffer = bytearray()\n                            self._handleLineRead(line, checkForResponseTerm=False)\n            #else:\n                #' <RX timeout>'\n        except serial.SerialException as e:\n            self.alive = False\n            try:\n                self.serial.close()\n            except Exception: #pragma: no cover\n                pass\n            # Notify the fatal error handler\n            self.fatalErrorCallback(e)\n\n    def write(self, data, waitForResponse=True, timeout=5, expectedResponseTermSeq=None):\n        data = data.encode()\n        with self._txLock:\n            if waitForResponse:\n                if expectedResponseTermSeq:\n                    self._expectResponseTermSeq = bytearray(expectedResponseTermSeq.encode())\n                self._response = []\n                self._responseEvent = threading.Event()\n                self.serial.write(data)\n                if self._responseEvent.wait(timeout):\n                    self._responseEvent = None\n                    self._expectResponseTermSeq = False\n                    return self._response\n                else: # Response timed out\n                    self._responseEvent = None\n                    self._expectResponseTermSeq = False\n                    if len(self._response) > 0:\n                        # Add the partial response to the timeout exception\n                        raise TimeoutException(self._response)\n                    else:\n                        raise TimeoutException()\n            else:\n                self.serial.write(data)\n"
  },
  {
    "path": "gsmmodem/util.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\" Some common utility classes used by tests \"\"\"\n\nfrom datetime import datetime, timedelta, tzinfo\nimport re\n\nclass SimpleOffsetTzInfo(tzinfo):    \n    \"\"\" Very simple implementation of datetime.tzinfo offering set timezone offset for datetime instances \"\"\"\n    \n    def __init__(self, offsetInHours=None):\n        \"\"\" Constructs a new tzinfo instance using an amount of hours as an offset\n        \n        :param offsetInHours: The timezone offset, in hours (may be negative)\n        :type offsetInHours: int or float\n        \"\"\"\n        if offsetInHours != None: #pragma: no cover\n            self.offsetInHours = offsetInHours        \n    \n    def utcoffset(self, dt):\n        return timedelta(hours=self.offsetInHours)\n    \n    def dst(self, dt):\n        return timedelta(0)\n    \n    def __repr__(self):\n        return 'gsmmodem.util.SimpleOffsetTzInfo({0})'.format(self.offsetInHours)\n\ndef parseTextModeTimeStr(timeStr):\n    \"\"\" Parses the specified SMS text mode time string\n    \n    The time stamp format is \"yy/MM/dd,hh:mm:ss±zz\"\n    (yy = year, MM = month, dd = day, hh = hour, mm = minute, ss = second, zz = time zone\n    [Note: the unit of time zone is a quarter of an hour])\n    \n    :param timeStr: The time string to parse\n    :type timeStr: str\n    \n    :return: datetime object representing the specified time string\n    :rtype: datetime.datetime\n    \"\"\"\n    msgTime = timeStr[:-3]\n    tzOffsetHours = int(int(timeStr[-3:]) * 0.25)\n    return datetime.strptime(msgTime, '%y/%m/%d,%H:%M:%S').replace(tzinfo=SimpleOffsetTzInfo(tzOffsetHours))\n\ndef lineStartingWith(string, lines):\n    \"\"\" Searches through the specified list of strings and returns the \n    first line starting with the specified search string, or None if not found\n    \"\"\"\n    for line in lines:\n        if line.startswith(string):\n            return line\n    else:\n        return None\n\ndef lineMatching(regexStr, lines):\n    \"\"\" Searches through the specified list of strings and returns the regular expression \n    match for the first line that matches the specified regex string, or None if no match was found\n\n    Note: if you have a pre-compiled regex pattern, use lineMatchingPattern() instead\n\n    :type regexStr: Regular expression string to use\n    :type lines: List of lines to search\n\n    :return: the regular expression match for the first line that matches the specified regex, or None if no match was found\n    :rtype: re.Match\n    \"\"\"\n    regex = re.compile(regexStr)\n    for line in lines:\n        m = regex.match(line)\n        if m:\n            return m\n    else:\n        return None\n\ndef lineMatchingPattern(pattern, lines):\n    \"\"\" Searches through the specified list of strings and returns the regular expression \n    match for the first line that matches the specified pre-compiled regex pattern, or None if no match was found\n\n    Note: if you are using a regex pattern string (i.e. not already compiled), use lineMatching() instead\n\n    :type pattern: Compiled regular expression pattern to use\n    :type lines: List of lines to search\n\n    :return: the regular expression match for the first line that matches the specified regex, or None if no match was found\n    :rtype: re.Match\n    \"\"\"\n    for line in lines:\n        m = pattern.match(line)\n        if m:\n            return m\n    else:\n        return None\n    \ndef allLinesMatchingPattern(pattern, lines):\n    \"\"\" Like lineMatchingPattern, but returns all lines that match the specified pattern\n\n    :type pattern: Compiled regular expression pattern to use\n    :type lines: List of lines to search\n\n    :return: list of re.Match objects for each line matched, or an empty list if none matched\n    :rtype: list\n    \"\"\"\n    result = []\n    for line in lines:\n        m = pattern.match(line)\n        if m:\n            result.append(m)\n    return result\n\n\ndef removeAtPrefix(string):\n    \"\"\" Remove AT prefix from a specified string.\n\n    :param string: An original string\n    :type string: str\n\n    :return: A string with AT prefix removed\n    :rtype: str\n    \"\"\"\n    if string.startswith('AT'):\n        return string[2:]\n    return string\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n    \"setuptools>=45\",\n    \"setuptools_scm[toml]>=6.2\",\n    \"wheel\",\n]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.setuptools_scm]\n\n[tool.black]\ntarget-version = [\"py38\", \"py39\", \"py310\"]\n\n[tool.isort]\nprofile = \"black\"\n"
  },
  {
    "path": "requirements.txt",
    "content": ".\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nname = python-gsmmodem-new\ndescription = Control an attached GSM modem: send/receive SMS messages, handle calls, etc\nlicense = LGPLv3+\nauthor = Francois Aucamp\nauthor_email = francois.aucamp@gmail.com\nurl = https://github.com/babca/python-gsmmodem\nlong_description = file: README.rst\nlong_description_content_type = text/x-rst\nclassifiers =\n    Development Status :: 4 - Beta\n    Environment :: Console\n    Intended Audience :: Developers\n    Intended Audience :: Telecommunications Industry\n    License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)\n    Operating System :: OS Independent\n    Programming Language :: Python :: 3\n    Topic :: Communications :: Telephony\n    Topic :: Home Automation\n    Topic :: Software Development :: Libraries :: Python Modules\n    Topic :: System :: Hardware\n    Topic :: Terminals :: Serial\n    Topic :: Utilities\nkeywords =\n    gsm\n    sms\n    modem\n    mobile\n    phone\n    usb\n    serial\n\n[options]\npackages =\n    gsmmodem\n    gsmtermlib\npackage_dir = gsmtermlib=tools/gsmtermlib\nscripts =\n    tools/gsmterm.py\n    tools/sendsms.py\n    tools/identify-modem.py\ninstall_requires = pyserial>=3.1.1\ntests_require =\n\n[options.extras_require]\ndocs = sphinx\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n\n\"\"\" python-gsmmodem installation script \"\"\"\n\nimport sys\nfrom distutils.core import Command\nfrom setuptools import setup\n\ntest_command = [sys.executable, '-m', 'unittest', 'discover']\ncoverage_command = ['coverage', 'run', '-m', 'unittest', 'discover']\n\nVERSION = \"0.12\"\n\nclass RunUnitTests(Command):\n    \"\"\" run unit tests \"\"\"\n\n    user_options = []\n    description = __doc__[1:]\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n    def run(self):\n        import subprocess\n        errno = subprocess.call(test_command)\n        raise SystemExit(errno)\n\nclass RunUnitTestsCoverage(Command):\n    \"\"\" run unit tests and report on code coverage using the 'coverage' tool \"\"\"\n\n    user_options = []\n    description = __doc__[1:]\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n    def run(self):\n        import subprocess\n        errno = subprocess.call(coverage_command)\n        if errno == 0:\n            subprocess.call(['coverage', 'report'])\n        raise SystemExit(errno)\n\nsetup(use_scm_version=True,\n      cmdclass = {'test': RunUnitTests,\n                  'coverage': RunUnitTestsCoverage})\n"
  },
  {
    "path": "test/__init__.py",
    "content": "\"\"\" Tests for python-gsmmodem \"\"\"\n"
  },
  {
    "path": "test/compat.py",
    "content": "\"\"\" Contains equivalents for a few commonly-used Python 2.7-and-higher test functions.\nUsed to provide backwards-compatibility with Python 2.6\n\"\"\"\nimport sys\nif sys.version_info[0] == 2 and sys.version_info[1] < 7:\n\n    import unittest\n\n    def assertGreater(self, a, b, msg=None):\n        \"\"\" Drop-in replacement for Python 2.7's method of the same name \"\"\"\n        return self.assertTrue(a > b, msg)\n    \n    def assertGreaterEqual(self, a, b, msg=None):\n        \"\"\" Drop-in replacement for Python 2.7's method of the same name \"\"\"\n        return self.assertTrue(a >= b, msg)\n    \n    def assertIsInstance(self, a, b, msg=None):\n        \"\"\" Drop-in replacement for Python 2.7's method of the same name \"\"\"\n        return self.assertTrue(isinstance(a, b), msg)\n    \n    def assertListEqual(self, a, b, msg=None):\n        \"\"\" Drop-in replacement for Python 2.7's method of the same name \"\"\"\n        if len(a) != len(b):\n            raise self.failureException(msg or 'List length differs')\n        else:\n            for i in xrange(len(a)):\n                if a[i] != b[i]:\n                    raise self.failureException(msg or 'List differs: {0} != {1}'.format(a[i], b[i]))    \n    \n    def assertIn(self, a, b, msg=None):\n        \"\"\" Drop-in replacement for Python 2.7's method of the same name \"\"\"\n        return self.assertTrue(a in b, msg)\n    \n    def assertNotIn(self, a, b, msg=None):\n        \"\"\" Drop-in replacement for Python 2.7's method of the same name \"\"\"\n        return self.assertTrue(a not in b, msg)\n    \n    def assertIs(self, a, b, msg=None):\n        \"\"\" Drop-in replacement for Python 2.7's method of the same name \"\"\"\n        return self.assertTrue(a is b, msg)\n    \n    # Monkey-patch our compatibility methods into unittest.TestCase\n    unittest.TestCase.assertGreater = assertGreater\n    unittest.TestCase.assertGreaterEqual = assertGreaterEqual\n    unittest.TestCase.assertIsInstance = assertIsInstance\n    unittest.TestCase.assertListEqual = assertListEqual\n    unittest.TestCase.assertIn = assertIn\n    unittest.TestCase.assertNotIn = assertNotIn\n    unittest.TestCase.assertIs = assertIs\nif sys.version_info[0] == 2:\n    str = str\n    bytearrayToStr = str\nelse:\n    str = lambda x: x\n    bytearrayToStr = lambda x: x.decode('latin-1')\n"
  },
  {
    "path": "test/fakemodems.py",
    "content": "\"\"\" Module containing fake modem descriptors, for testing \"\"\"\n\nimport abc\nfrom copy import copy\n\nclass FakeModem(object):\n    \"\"\" Abstract base class for fake modem descriptors \"\"\"\n    __metaclass__ = abc.ABCMeta\n\n    def __init__(self):\n        self.responses = {}\n        self.commandsNoPinRequired = []\n        self.commandsSimBusy = [] # Commands that may trigger \"SIM busy\" errors\n        self.pinLock = False\n        self.defaultResponse = ['OK\\r\\n']\n        self.pinRequiredErrorResponse = ['+CME ERROR: 11\\r\\n']\n        self.smscNumber = None\n        self.simBusyErrorCounter = 0 # Number of times to issue a \"SIM busy\" error\n        self.deviceBusyErrorCounter = 0 # Number of times to issue a \"Device busy\" error\n        self.cfun = 1 # +CFUN value to report back\n        self.dtmfCommandBase = '+VTS='\n\n    def getResponse(self, cmd):\n        if type(cmd) == bytes:\n            cmd = cmd.decode()\n\n        if self.deviceBusyErrorCounter > 0:\n            self.deviceBusyErrorCounter -= 1\n            return ['+CME ERROR: 515\\r\\n']\n        if self._pinLock and not cmd.startswith('AT+CPIN'):\n            if cmd not in self.commandsNoPinRequired:\n                return copy(self.pinRequiredErrorResponse)\n\n        if cmd.startswith('AT+CPIN=\"'):\n            self.pinLock = False\n        elif self.simBusyErrorCounter > 0 and cmd in self.commandsSimBusy:\n            self.simBusyErrorCounter -= 1\n            return ['+CME ERROR: 14\\r\\n']\n        if cmd == 'AT+CFUN?\\r' and self.cfun != -1:\n            return ['+CFUN: {0}\\r\\n'.format(self.cfun), 'OK\\r\\n']\n        elif cmd == 'AT+CSCA?\\r':\n            if self.smscNumber != None:\n                return ['+CSCA: \"{0}\",145\\r\\n'.format(self.smscNumber), 'OK\\r\\n']\n            else:\n                return ['OK\\r\\n']\n        if cmd in self.responses:\n            return copy(self.responses[cmd])\n        else:\n            return copy(self.defaultResponse)\n\n    @property\n    def pinLock(self):\n        return self._pinLock\n    @pinLock.setter\n    def pinLock(self, pinLock):\n        self._pinLock = pinLock\n        if self._pinLock == True:\n            self.responses['AT+CPIN?\\r'] = ['+CPIN: SIM PIN\\r\\n', 'OK\\r\\n']\n        else:\n            self.responses['AT+CPIN?\\r'] = ['+CPIN: READY\\r\\n', 'OK\\r\\n']\n\n    @abc.abstractmethod\n    def getAtdResponse(self, number):\n        return []\n\n    @abc.abstractmethod\n    def getPreCallInitWaitSequence(self):\n        return [0.1]\n\n    @abc.abstractmethod\n    def getCallInitNotification(self, callId, callType):\n        return ['+WIND: 5,1\\r\\n', '+WIND: 2\\r\\n']\n\n    @abc.abstractmethod\n    def getRemoteAnsweredNotification(self, callId, callType):\n        return ['OK\\r\\n']\n\n    @abc.abstractmethod\n    def getRemoteHangupNotification(self, callId, callType):\n        return ['NO CARRIER\\r\\n', '+WIND: 6,1\\r\\n']\n\n    def getRemoteRejectCallNotification(self, callId, callType):\n        # For a lot of modems, this is the same as a hangup notification - override this if necessary!\n        return self.getRemoteHangupNotification(callId, callType)\n\n    @abc.abstractmethod\n    def getIncomingCallNotification(self, callerNumber, callType='VOICE', ton=145):\n        return ['RING\\r\\n']\n\n\nclass GenericTestModem(FakeModem):\n    \"\"\" Not based on a real modem - simply used for general tests. Uses polling for call status updates \"\"\"\n\n    def __init__(self):\n        super(GenericTestModem, self).__init__()\n        self._callState = 2\n        self._callNumber = None\n        self._callId = None\n        self.commandsNoPinRequired = ['ATZ\\r', 'ATE0\\r', 'AT+CFUN?\\r', 'AT+CFUN=1\\r', 'AT+CMEE=1\\r']\n        self.responses = {'AT+CPMS=?\\r': ['+CPMS: (\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\")\\r\\n', 'OK\\r\\n'],\n                          'AT+CLAC=?\\r': ['ERROR\\r\\n'],\n                          'AT+CLAC\\r': ['ERROR\\r\\n'],\n                          'AT+WIND=?\\r': ['ERROR\\r\\n'],\n                          'AT+WIND?\\r': ['ERROR\\r\\n'],\n                          'AT+WIND=50\\r': ['ERROR\\r\\n'],\n                          'AT+ZPAS=?\\r': ['ERROR\\r\\n'],\n                          'AT+ZPAS?\\r': ['ERROR\\r\\n'],\n                          'AT+CSCS=?\\r': ['+CSCS: (\"GSM\",\"UCS2\")\\r\\n', 'OK\\r\\n'],\n                          'AT+CPIN?\\r': ['+CPIN: READY\\r\\n', 'OK\\r\\n'],\n                          'AT\\r': ['OK\\r\\n']}\n\n    def getResponse(self, cmd):\n        if type(cmd) == bytes:\n            cmd = cmd.decode()\n\n        if not self._pinLock and cmd == 'AT+CLCC\\r':\n            if self._callNumber:\n                if self._callState == 0:\n                    return ['+CLCC: 1,0,2,0,0,\"{0}\",129\\r\\n'.format(self._callNumber), 'OK\\r\\n']\n                elif self._callState == 1:\n                    return ['+CLCC: 1,0,0,0,0,\"{0}\",129\\r\\n'.format(self._callNumber), 'OK\\r\\n']\n                else:\n                    return ['OK\\r\\n']\n            else:\n                return super(GenericTestModem, self).getResponse(cmd)\n        else:\n            return super(GenericTestModem, self).getResponse(cmd)\n\n    def getAtdResponse(self, number):\n        self._callNumber = number\n        self._callState = 0\n        return ['OK\\r\\n']\n\n    def getPreCallInitWaitSequence(self):\n        return [0.1]\n\n    def getCallInitNotification(self, callId, callType):\n        return []\n\n    def getRemoteAnsweredNotification(self, callId, callType):\n        self._callState = 1\n        return []\n\n    def getRemoteHangupNotification(self, callId, callType):\n        self._callState = 2\n        self._callNumber = None\n        return []\n\n    def getIncomingCallNotification(self, callerNumber, callType='VOICE', ton=145):\n        return ['+CRING: {0}\\r\\n'.format(callType), '+CLIP: \"{1}\",{2},,,,0\\r\\n'.format(callType, callerNumber, ton)]\n\n\nclass WavecomMultiband900E1800(FakeModem):\n    \"\"\" Family of old Wavecom serial modems\n\n    User franciumlin also submitted the following improvements to this profile:\n      +CPIN replies are not ended with \"OK\"\n    \"\"\"\n\n    def __init__(self):\n        super(WavecomMultiband900E1800, self).__init__()\n        self.responses = {'AT+CGMI\\r': [' WAVECOM MODEM\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMM\\r': [' MULTIBAND  900E  1800\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMR\\r': ['ERROR\\r\\n'],\n                 'AT+CIMI\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CGSN\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CLAC\\r': ['ERROR\\r\\n'],\n                 'AT+WIND?\\r': ['+WIND: 0\\r\\n', 'OK\\r\\n'],\n                 'AT+WIND=50\\r': ['OK\\r\\n'],\n                 'AT+ZPAS?\\r': ['ERROR\\r\\n'],\n                 'AT+CPMS=\"SM\",\"SM\",\"SR\"\\r': ['ERROR\\r\\n'],\n                 'AT+CPMS=?\\r': ['+CPMS: ((\"SM\",\"BM\",\"SR\"),(\"SM\"))\\r\\n', 'OK\\r\\n'],\n                 'AT+CPMS=\"SM\",\"SM\"\\r': ['+CPMS: 14,50,14,50\\r\\n', 'OK\\r\\n'],\n                 'AT+CNMI=2,1,0,2\\r': ['OK\\r\\n'],\n                 'AT+CVHU=0\\r': ['ERROR\\r\\n'],\n                 'AT+CPIN?\\r': ['+CPIN: READY\\r\\n']} # <---- note: missing 'OK\\r\\n'\n        self.commandsNoPinRequired = ['ATZ\\r', 'ATE0\\r', 'AT+CFUN?\\r', 'AT+CFUN=1\\r', 'AT+CMEE=1\\r']\n\n    def getResponse(self, cmd):\n        if type(cmd) == bytes:\n            cmd = cmd.decode()\n\n        if cmd == 'AT+CFUN=1\\r':\n            self.deviceBusyErrorCounter = 2 # This modem takes quite a while to recover from this\n            return ['OK\\r\\n']\n        return super(WavecomMultiband900E1800, self).getResponse(cmd)\n\n    @property\n    def pinLock(self):\n        return self._pinLock\n    @pinLock.setter\n    def pinLock(self, pinLock):\n        self._pinLock = pinLock\n        if self._pinLock == True:\n            self.responses['AT+CPIN?\\r'] = ['+CPIN: SIM PIN\\r\\n']  # missing OK\n        else:\n            self.responses['AT+CPIN?\\r'] = ['+CPIN: READY\\r\\n'] # missing OK\n\n    def getAtdResponse(self, number):\n        return []\n\n    def getPreCallInitWaitSequence(self):\n        return [0.1]\n\n    def getCallInitNotification(self, callId, callType):\n        # +WIND: 5 == indication of call\n        # +WIND: 2 == remote party is ringing\n        return ['+WIND: 5,1\\r\\n', '+WIND: 2\\r\\n']\n\n    def getRemoteAnsweredNotification(self, callId, callType):\n        return ['OK\\r\\n']\n\n    def getRemoteHangupNotification(self, callId, callType):\n        return ['NO CARRIER\\r\\n', '+WIND: 6,1\\r\\n']\n\n    def getIncomingCallNotification(self, callerNumber, callType='VOICE', ton=145):\n        return ['+CRING: {0}\\r\\n'.format(callType), '+CLIP: \"{1}\",{2}\\r\\n'.format(callType, callerNumber, ton)]\n\n    def __str__(self):\n        return 'WAVECOM MODEM MULTIBAND 900E 1800'\n\n\nclass HuaweiK3715(FakeModem):\n    \"\"\" Huawei K3715 modem (commonly used by Vodafone) \"\"\"\n\n    def __init__(self):\n        super(HuaweiK3715, self).__init__()\n        self.responses = {'AT+CGMI\\r': ['huawei\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMM\\r': ['K3715\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMR\\r': ['11.104.05.00.00\\r\\n', 'OK\\r\\n'],\n                 'AT+CIMI\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CGSN\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CPMS=?\\r': ['+CPMS: (\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\")\\r\\n', 'OK\\r\\n'],\n                 'AT+WIND?\\r': ['ERROR\\r\\n'],\n                 'AT+WIND=50\\r': ['ERROR\\r\\n'],\n                 'AT+ZPAS?\\r': ['ERROR\\r\\n'],\n                 'AT+CLAC\\r': ['+CLAC:&C,&D,&E,&F,&S,&V,&W,E,I,L,M,Q,V,X,Z,T,P,\\S,\\V,\\\n%V,D,A,H,O,S0,S2,S3,S4,S5,S6,S7,S8,S9,S10,S11,S30,S103,S104,+FCLASS,+ICF,+IFC,+IPR,+GMI,\\\n+GMM,+GMR,+GCAP,+GSN,+DR,+DS,+WS46,+CLAC,+CCLK,+CBST,+CRLP,+CV120,+CHSN,+CSSN,+CREG,+CGREG,\\\n+CFUN,+GCAP,+CSCS,+CSTA,+CR,+CEER,+CRC,+CMEE,+CGDCONT,+CGDSCONT,+CGTFT,+CGEQREQ,+CGEQMIN,\\\n+CGQREQ,+CGQMIN,+CGEQNEG,+CGEREP,+CGPADDR,+CGCLASS,+CGSMS,+CSMS,+CMGF,+CSAS,+CRES,+CSCA,\\\n+CSMP,+CSDH,+CSCB,+FDD,+FAR,+FCL,+FIT,+ES,+ESA,+CMOD,+CVHU,+CGDATA,+CSQ,+CBC,+CPAS,+CPIN,\\\n+CMEC,+CGATT,+CGACT,+CGCMOD,+CPBS,+CPBR,+CPBF,+CPBW,+CPMS,+CNMI,+CMGL,+CMGR,+CMGS,+CMSS,\\\n+CMGW,+CMGD,+CMGC,+CNMA,+CMMS,+FTS,+FRS,+FTH,+FRH,+FTM,+FRM,+CHUP,+CCFC,+CCUG,+COPS,+CLCK,\\\n+CPWD,+CUSD,+CAOC,+CACM,+CAMM,+CPUC,+CCWA,+CHLD,+CIMI,+CGMI,+CGMM,+CGMR,+CGSN,+CNUM,+CSIM,\\\n+CRSM,+CCLK,+CLVL,+CMUT,+CLCC,+COPN,+CPOL,+CPLS,+CTZR,+CTZU,+CLAC,+CLIP,+COLP,+CDIP,+CTFR,\\\n+CLIR,$QCSIMSTAT,$QCCNMI,$QCCLR,$QCDMG,$QCDMR,$QCDNSP,$QCDNSS,$QCTER,$QCSLOT,$QCPINSTAT,$QCPDPP,\\\n$QCPDPLT,$QCPWRDN,$QCDGEN,$BREW,$QCSYSMODE,^CVOICE,^DDSETEX,^pcmrecord,^SYSINFO,^SYSCFG,^IMSICHG,\\\n^HS,^DTMF,^EARST,^CDUR,^LIGHT,^CPBR,^CPBW,^HWVER,^HVER,^DSFLOWCLR,^DSFLOWQRY,^DSFLOWRPT,^SPN,\\\n^PORTSEL,^CPIN,^PNN,^OPL,^CPNN,^SN,^CARDLOCK,^BOOT,^FHVER,^CURC,^FREQLOCK,^HSDPA,^HSUPA,^CARDMODE,\\\n^U2DIAG,^CELLMODE,^HSPA,^SCSIOVERTIME,^SETPID,^ADCTEMP,^OPWORD,^CPWORD,^DISLOG,^ANQUERY,^RSCPCFG,^ECIOCFG,\\r\\n', 'OK\\r\\n'],\n                 'AT+CPIN?\\r': ['+CPIN: READY\\r\\n', 'OK\\r\\n']}\n        self.commandsNoPinRequired = ['ATZ\\r', 'ATE0\\r', 'AT+CFUN?\\r', 'AT+CFUN=1\\r', 'AT+CMEE=1\\r']\n        self.dtmfCommandBase = '^DTMF={cid},'\n\n    def getAtdResponse(self, number):\n        return ['OK\\r\\n']\n\n    def getPreCallInitWaitSequence(self):\n        return [0.1]\n\n    def getCallInitNotification(self, callId, callType):\n        return ['^ORIG:{0},{1}\\r\\n'.format(callId, callType), 0.2, '^CONF:{0}\\r\\n'.format(callId)]\n\n    def getRemoteAnsweredNotification(self, callId, callType):\n        return ['^CONN:{0},{1}\\r\\n'.format(callId, callType)]\n\n    def getRemoteHangupNotification(self, callId, callType):\n            return ['^CEND:{0},5,29,16\\r\\n'.format(callId)]\n\n    def getIncomingCallNotification(self, callerNumber, callType='VOICE', ton=145):\n        return ['+CRING: {0}\\r\\n'.format(callType), '+CLIP: \"{1}\",{2},,,,0\\r\\n'.format(callType, callerNumber, ton)]\n\n    def __str__(self):\n        return 'Huawei K3715'\n\n\nclass HuaweiE1752(FakeModem):\n    \"\"\" Huawei E1752 modem (used by Cell C in South Africa)\n    This modem issues \"COMMAND NOT SUPPORT\" non-standard error messages\n    \"\"\"\n\n    def __init__(self):\n        super(HuaweiE1752, self).__init__()\n        # This modem uses AT^USSDMODE to control text/PDU mode USSD\n        self._ussdMode = 1\n        self.responses = {'AT+CGMI\\r': ['huawei\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMM\\r': ['E1752\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMR\\r': ['11.126.13.00.00\\r\\n', 'OK\\r\\n'],\n                 'AT+CIMI\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CGSN\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CPMS=?\\r': ['+CPMS: (\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\")\\r\\n', 'OK\\r\\n'],\n                 # Note the non-standard \"COMMAND NOT SUPPORT\" error message\n                 'AT+WIND?\\r': ['COMMAND NOT SUPPORT\\r\\n'],\n                 'AT+WIND=50\\r': ['COMMAND NOT SUPPORT\\r\\n'],\n                 'AT+ZPAS?\\r': ['COMMAND NOT SUPPORT\\r\\n'],\n                 # Modem has non-standard +CLAC response (does not start with +CLAC:, and extra \\r added to each line (i.e. as part of the command name)\n                 'AT+CLAC\\r': ['&C\\r\\r\\n', '&D\\r\\r\\n', '&F\\r\\r\\n', '&V\\r\\r\\n', 'E\\r\\r\\n', 'I\\r\\r\\n', 'L\\r\\r\\n', 'M\\r\\r\\n',\n                               'Q\\r\\r\\n', 'V\\r\\r\\n', 'X\\r\\r\\n', 'Z\\r\\r\\n', 'T\\r\\r\\n', 'P\\r\\r\\n', 'D\\r\\r\\n', 'A\\r\\r\\n',\n                               'H\\r\\r\\n', 'O\\r\\r\\n', 'S0\\r\\r\\n', 'S2\\r\\r\\n', 'S3\\r\\r\\n', 'S4\\r\\r\\n', 'S5\\r\\r\\n', 'S6\\r\\r\\n',\n                               'S7\\r\\r\\n', 'S8\\r\\r\\n', 'S9\\r\\r\\n', 'S10\\r\\r\\n', 'S11\\r\\r\\n', 'S30\\r\\r\\n', 'S103\\r\\r\\n',\n                               'S104\\r\\r\\n', '+FCLASS\\r\\r\\n', '+ICF\\r\\r\\n', '+IFC\\r\\r\\n', '+IPR\\r\\r\\n', '+GMI\\r\\r\\n',\n                               '+GMM\\r\\r\\n', '+GMR\\r\\r\\n', '+GCAP\\r\\r\\n', '+GSN\\r\\r\\n', '+DR\\r\\r\\n', '+DS\\r\\r\\n',\n                               '+WS46\\r\\r\\n', '+CLAC\\r\\r\\n', '+CCLK\\r\\r\\n', '+CBST\\r\\r\\n', '+CRLP\\r\\r\\n', '+CV120\\r\\r\\n',\n                               '+CHSN\\r\\r\\n', '+CSSN\\r\\r\\n', '+CREG\\r\\r\\n', '+CGREG\\r\\r\\n', '+CFUN\\r\\r\\n', '+GCAP\\r\\r\\n',\n                               '+CSCS\\r\\r\\n', '+CSTA\\r\\r\\n', '+CR\\r\\r\\n', '+CEER\\r\\r\\n', '+CRC\\r\\r\\n', '+CMEE\\r\\r\\n',\n                               '+CGDCONT\\r\\r\\n', '+CGDSCONT\\r\\r\\n', '+CGTFT\\r\\r\\n', '+CGEQREQ\\r\\r\\n', '+CGEQMIN\\r\\r\\n',\n                               '+CGQREQ\\r\\r\\n', '+CGQMIN\\r\\r\\n', '+CGEQNEG\\r\\r\\n', '+CGEREP\\r\\r\\n', '+CGPADDR\\r\\r\\n',\n                               '+CGCLASS\\r\\r\\n', '+CGSMS\\r\\r\\n', '+CSMS\\r\\r\\n', '+CMGF\\r\\r\\n', '+CSAS\\r\\r\\n', '+CRES\\r\\r\\n',\n                               '+CSCA\\r\\r\\n', '+CSMP\\r\\r\\n', '+CSDH\\r\\r\\n', '+CSCB\\r\\r\\n', '+FDD\\r\\r\\n', '+FAR\\r\\r\\n',\n                               '+FCL\\r\\r\\n', '+FIT\\r\\r\\n', '+ES\\r\\r\\n', '+ESA\\r\\r\\n', '+CMOD\\r\\r\\n', '+CVHU\\r\\r\\n',\n                               '+CGDATA\\r\\r\\n', '+CSQ\\r\\r\\n', '+CBC\\r\\r\\n', '+CPAS\\r\\r\\n', '+CPIN\\r\\r\\n', '+CMEC\\r\\r\\n',\n                               '+CKPD\\r\\r\\n', '+CIND\\r\\r\\n', '+CMER\\r\\r\\n', '+CGATT\\r\\r\\n', '+CGACT\\r\\r\\n', '+CGCMOD\\r\\r\\n',\n                               '+CPBS\\r\\r\\n', '+CPBR\\r\\r\\n', '+CPBF\\r\\r\\n', '+CPBW\\r\\r\\n', '+CPMS\\r\\r\\n', '+CNMI\\r\\r\\n',\n                               '+CMGL\\r\\r\\n', '+CMGR\\r\\r\\n', '+CMGS\\r\\r\\n', '+CMSS\\r\\r\\n', '+CMGW\\r\\r\\n', '+CMGD\\r\\r\\n',\n                               '+CMGC\\r\\r\\n', '+CNMA\\r\\r\\n', '+CMMS\\r\\r\\n', '+FTS\\r\\r\\n', '+FRS\\r\\r\\n', '+FTH\\r\\r\\n',\n                               '+FRH\\r\\r\\n', '+FTM\\r\\r\\n', '+FRM\\r\\r\\n', '+CHUP\\r\\r\\n', '+CCFC\\r\\r\\n', '+CCUG\\r\\r\\n',\n                               '+COPS\\r\\r\\n', '+CLCK\\r\\r\\n', '+CPWD\\r\\r\\n', '+CUSD\\r\\r\\n', '+CAOC\\r\\r\\n', '+CACM\\r\\r\\n',\n                               '+CAMM\\r\\r\\n', '+CPUC\\r\\r\\n', '+CCWA\\r\\r\\n', '+CHLD\\r\\r\\n', '+CIMI\\r\\r\\n', '+CGMI\\r\\r\\n',\n                               '+CGMM\\r\\r\\n', '+CGMR\\r\\r\\n', '+CGSN\\r\\r\\n', '+CNUM\\r\\r\\n', '+CSIM\\r\\r\\n', '+CRSM\\r\\r\\n',\n                               '+CCLK\\r\\r\\n', '+CLVL\\r\\r\\n', '+CMUT\\r\\r\\n', '+CLCC\\r\\r\\n', '+COPN\\r\\r\\n', '+CPOL\\r\\r\\n',\n                               '+CPLS\\r\\r\\n', '+CTZR\\r\\r\\n', '+CTZU\\r\\r\\n', '+CLAC\\r\\r\\n', '+CLIP\\r\\r\\n', '+COLP\\r\\r\\n',\n                               '+CDIP\\r\\r\\n', '+CTFR\\r\\r\\n', '+CLIR\\r\\r\\n', '$QCSIMSTAT\\r\\r\\n', '$QCCNMI\\r\\r\\n',\n                               '$QCCLR\\r\\r\\n', '$QCDMG\\r\\r\\n', '$QCDMR\\r\\r\\n', '$QCDNSP\\r\\r\\n', '$QCDNSS\\r\\r\\n',\n                               '$QCTER\\r\\r\\n', '$QCSLOT\\r\\r\\n', '$QCPINSTAT\\r\\r\\n', '$QCPDPP\\r\\r\\n', '$QCPDPLT\\r\\r\\n',\n                               '$QCPWRDN\\r\\r\\n', '$QCDGEN\\r\\r\\n', '$BREW\\r\\r\\n', '$QCSYSMODE\\r\\r\\n', '$QCCTM\\r\\r\\n',\n                               '^RFSWITCH\\r\\r\\n', '^SOFTSWITCH\\r\\r\\n', '^FLIGHTMODESAVE\\r\\r\\n', '^IMSICHG\\r\\r\\n',\n                               '^STSF\\r\\r\\n', '^STGI\\r\\r\\n', '^STGR\\r\\r\\n', '^CELLMODE\\r\\r\\n', '^SYSINFO\\r\\r\\n',\n                               '^DIALMODE\\r\\r\\n', '^SYSCFG\\r\\r\\n', '^SYSCONFIG\\r\\r\\n', '^HS\\r\\r\\n', '^DTMF\\r\\r\\n',\n                               '^CPBR\\r\\r\\n', '^CPBW\\r\\r\\n', '^HWVER\\r\\r\\n', '^HVER\\r\\r\\n', '^DSFLOWCLR\\r\\r\\n',\n                               '^DSFLOWQRY\\r\\r\\n', '^DSFLOWRPT\\r\\r\\n', '^SPN\\r\\r\\n', '^PORTSEL\\r\\r\\n', '^CPIN\\r\\r\\n',\n                               '^SN\\r\\r\\n', '^EARST\\r\\r\\n', '^CARDLOCK\\r\\r\\n', '^CARDUNLOCK\\r\\r\\n', '^ATRECORD\\r\\r\\n',\n                               '^CDUR\\r\\r\\n', '^BOOT\\r\\r\\n', '^FHVER\\r\\r\\n', '^CURC\\r\\r\\n', '^FREQLOCK\\r\\r\\n',\n                               '^FREQPREF\\r\\r\\n', '^HSPA\\r\\r\\n', '^HSUPA\\r\\r\\n', '^GPSTYPE\\r\\r\\n', '^HSDPA\\r\\r\\n',\n                               '^GLASTERR\\r\\r\\n', '^CARDMODE\\r\\r\\n', '^U2DIAG\\r\\r\\n', '^RSTRIGGER\\r\\r\\n', '^SETPID\\r\\r\\n',\n                               '^SCSITIMEOUT\\r\\r\\n', '^CQI\\r\\r\\n', '^GETPORTMODE\\r\\r\\n', '^CVOICE\\r\\r\\n', '^DDSETEX\\r\\r\\n',\n                               '^pcmrecord\\r\\r\\n', '^CSNR\\r\\r\\n', '^CMSR\\r\\r\\n', '^CMMT\\r\\r\\n', '^CMGI\\r\\r\\n', '^RDCUST\\r\\r\\n',\n                               '^OPWORD\\r\\r\\n', '^CPWORD\\r\\r\\n', '^DISLOG\\r\\r\\n', '^FPLMN\\r\\r\\n', '^FPLMNCTRL\\r\\r\\n',\n                               '^ANQUERY\\r\\r\\n', '^RSCPCFG\\r\\r\\n', '^ECIOCFG\\r\\r\\n', '^IMSICHECK\\r\\r\\n', '^USSDMODE\\r\\r\\n',\n                               '^SLOTCFG\\r\\r\\n', '^YJCX\\r\\r\\n', '^NDISDUP\\r\\r\\n', '^DHCP\\r\\r\\n', '^AUTHDATA\\r\\r\\n',\n                               '^CRPN\\r\\r\\n', '^ICCID\\r\\r\\n', '^NVMBN\\r\\r\\n', '^RXDIV\\r\\r\\n', '^DNSP\\r\\r\\n', '^DNSS\\r\\r\\n',\n                               '^WPDST\\r\\r\\n', '^WPDOM\\r\\r\\n', '^WPDFR\\r\\r\\n', '^WPQOS\\r\\r\\n', '^WPDSC\\r\\r\\n', '^WPDGP\\r\\r\\n',\n                               '^WPEND\\r\\r\\n', '^WNICT\\r\\r\\n', '^SOCKETCONT\\r\\r\\n', '^WPURL\\r\\r\\n', '^WMOLR\\r\\r\\n',\n                               '^SECTIME\\r\\r\\n', '^WPDNP\\r\\r\\n', '^WPDDL\\r\\r\\n', '^WPDCP\\r\\r\\n', 'OK\\r\\n'],\n                 'AT+CPIN?\\r': ['+CPIN: READY\\r\\n', 'OK\\r\\n']}\n        self.commandsNoPinRequired = ['ATZ\\r', 'ATE0\\r', 'AT+CFUN?\\r', 'AT+CFUN=1\\r', 'AT+CMEE=1\\r']\n        self.dtmfCommandBase = '^DTMF={cid},'\n\n    def getResponse(self, cmd):\n        if type(cmd) == bytes:\n            cmd = cmd.decode()\n\n        # Device defaults to ^USSDMODE == 1\n        if cmd.startswith('AT+CUSD=1') and self._ussdMode == 1:\n            return ['ERROR\\r\\n']\n        elif cmd.startswith('AT^USSDMODE='):\n            self._ussdMode = int(cmd[12])\n            return super(HuaweiE1752, self).getResponse(cmd)\n        else:\n            return super(HuaweiE1752, self).getResponse(cmd)\n\n    def getAtdResponse(self, number):\n        return ['OK\\r\\n']\n\n    def getPreCallInitWaitSequence(self):\n        return [0.1]\n\n    def getCallInitNotification(self, callId, callType):\n        return ['^ORIG:{0},{1}\\r\\n'.format(callId, callType), 0.2, '^CONF:{0}\\r\\n'.format(callId)]\n\n    def getRemoteAnsweredNotification(self, callId, callType):\n        return ['^CONN:{0},{1}\\r\\n'.format(callId, callType)]\n\n    def getRemoteHangupNotification(self, callId, callType):\n            return ['^CEND:{0},5,29,16\\r\\n'.format(callId)]\n\n    def getIncomingCallNotification(self, callerNumber, callType='VOICE', ton=145):\n        return ['+CRING: {0}\\r\\n'.format(callType), '+CLIP: \"{1}\",{2},,,,0\\r\\n'.format(callType, callerNumber, ton)]\n\n    def __str__(self):\n        return 'Huawei E1752'\n\n\nclass QualcommM6280(FakeModem):\n    \"\"\" Qualcomm/ZTE modem information provided by davidphiliplee on github \"\"\"\n\n    def __init__(self):\n        super(QualcommM6280, self).__init__()\n        self._callState = 2\n        self._callNumber = None\n        self._callId = None\n        self.commandsNoPinRequired = [] # This modem requires the CPIN command to be issued first\n        self.commandsSimBusy = ['AT+CSCA?\\r'] # Issue #10 on github\n        self.responses = {'AT+CGMI\\r': ['QUALCOMM INCORPORATED\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMM\\r': ['M6280\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMR\\r': ['M6280_V1.0.0 M6280_V1.0.0 1 [Sep 4 2008 12:00:00]\\r\\n', 'OK\\r\\n'],\n                 'AT+CIMI\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CGSN\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CLAC=?\\r': ['ERROR\\r\\n'],\n                 'AT+CLAC\\r': ['ERROR\\r\\n'],\n                 'AT+WIND=?\\r': ['ERROR\\r\\n'],\n                 'AT+WIND?\\r': ['ERROR\\r\\n'],\n                 'AT+WIND=50\\r': ['ERROR\\r\\n'],\n                 'AT+ZPAS?\\r':  ['+BEARTYPE: \"UMTS\",\"CS_PS\"\\r\\n', 'OK\\r\\n'],\n                 'AT+CPMS=?\\r': ['+CPMS: (\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\")\\r\\n', 'OK\\r\\n'],\n                 'AT+CVHU=0\\r': ['+CVHU: (0-1)\\r\\n', 'OK\\r\\n'],\n                 'AT+CPIN?\\r': ['+CPIN: READY\\r\\n', 'OK\\r\\n']}\n\n    def getResponse(self, cmd):\n        if type(cmd) == bytes:\n            cmd = cmd.decode()\n\n        if not self._pinLock:\n            if cmd.startswith('AT+CSMP='):\n                # Clear the SMSC number (this behaviour was reported in issue #8 on github)\n                self.smscNumber = None\n            elif cmd == 'AT+CLCC\\r':\n                if self._callNumber:\n                    if self._callState == 0:\n                        return ['+CLCC: 1,0,2,0,0,\"{0}\",129\\r\\n'.format(self._callNumber), 'OK\\r\\n']\n                    elif self._callState == 1:\n                        return ['+CLCC: 1,0,0,0,0,\"{0}\",129\\r\\n'.format(self._callNumber), 'OK\\r\\n']\n                    else:\n                        return ['OK\\r\\n']\n            return super(QualcommM6280, self).getResponse(cmd)\n        else:\n            return super(QualcommM6280, self).getResponse(cmd)\n\n    def getAtdResponse(self, number):\n        self._callNumber = number\n        self._callState = 0\n        return []\n\n    def getPreCallInitWaitSequence(self):\n        return [0.1]\n\n    def getCallInitNotification(self, callId, callType):\n        return []\n\n    def getRemoteAnsweredNotification(self, callId, callType):\n        return ['CONNECT\\r\\n']\n\n    def getRemoteHangupNotification(self, callId, callType):\n        self._callState = 2\n        self._callNumber = None\n        return ['HANGUP: {0}\\r\\n'.format(callId)]\n\n    def getIncomingCallNotification(self, callerNumber, callType='VOICE', ton=145):\n        return ['+CRING: {0}\\r\\n'.format(callType), '+CLIP: \"{1}\",{2},,,,0\\r\\n'.format(callType, callerNumber, ton)]\n\n    def __str__(self):\n        return 'QUALCOMM M6280 (ZTE modem)'\n\n\nclass ZteK3565Z(FakeModem):\n    \"\"\" ZTE K3565-Z (Vodafone branded) \"\"\"\n\n    def __init__(self):\n        super(ZteK3565Z, self).__init__()\n        self._callState = 2\n        self._callNumber = None\n        self._callId = None\n        self.commandsNoPinRequired = [] # This modem requires the CPIN command to be issued first\n        self.responses = {'AT+CGMI\\r': ['ZTE INCORPORATED\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMM\\r': ['K3565-Z\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMR\\r': ['BD_P673A2V1.0.0B09\\r\\n', 'OK\\r\\n'],\n                 'AT+CFUN?\\r': ['+CFUN: (0-1,4-7),(0-1)\\r\\n', 'OK\\r\\n'],\n                 'AT+CIMI\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CGSN\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 # Note that AT+CLAC does NOT respond in the standard \"+CLAC:\" format\n                 'AT+CLAC\\r': ['&C\\r\\n', '&D\\r\\n', '&E\\r\\n', '&F\\r\\n', '&S\\r\\n', '&V\\r\\n', '&W\\r\\n', 'E\\r\\n', 'I\\r\\n',\n                               'L\\r\\n', 'M\\r\\n', 'Q\\r\\n', 'V\\r\\n', 'X\\r\\n', 'Z\\r\\n', 'T\\r\\n', 'P\\r\\n', '\\\\Q\\r\\n', '\\\\S\\r\\n',\n                               '\\\\V\\r\\n', '%V\\r\\n', 'D\\r\\n', 'A\\r\\n', 'H\\r\\n', 'O\\r\\n', 'S0\\r\\n', 'S2\\r\\n', 'S3\\r\\n', 'S4\\r\\n',\n                               'S5\\r\\n', 'S6\\r\\n', 'S7\\r\\n', 'S8\\r\\n', 'S9\\r\\n', 'S10\\r\\n', 'S11\\r\\n', 'S30\\r\\n', 'S103\\r\\n',\n                               'S104\\r\\n', '+FCLASS\\r\\n', '+ICF\\r\\n', '+IFC\\r\\n', '+IPR\\r\\n', '+GMI\\r\\n', '+GMM\\r\\n',\n                               '+GMR\\r\\n', '+GCAP\\r\\n', '+GSN\\r\\n', '+DR\\r\\n', '+DS\\r\\n', '+WS46\\r\\n', '+CBST\\r\\n', '+CRLP\\r\\n',\n                               '+CV120\\r\\n', '+CHSN\\r\\n', '+CSSN\\r\\n', '+CREG\\r\\n', '+CGREG\\r\\n', '+CFUN\\r\\n', '+GCAP\\r\\n',\n                               '+CSCS\\r\\n', '+CSTA\\r\\n', '+CR\\r\\n', '+CEER\\r\\n', '+CRC\\r\\n', '+CMEE\\r\\n', '+CGDCONT\\r\\n',\n                               '+CGDSCONT\\r\\n', '+CGTFT\\r\\n', '+CGEQREQ\\r\\n', '+CGEQMIN\\r\\n', '+CGQREQ\\r\\n', '+CGQMIN\\r\\n',\n                               '+CGEREP\\r\\n', '+CGPADDR\\r\\n', '+CGDATA\\r\\n', '+CGCLASS\\r\\n', '+CGSMS\\r\\n', '+CSMS\\r\\n',\n                               '+CMGF\\r\\n', '+CSAS\\r\\n', '+CRES\\r\\n', '+CSCA\\r\\n', '+CSMP\\r\\n', '+CSDH\\r\\n', '+CSCB\\r\\n',\n                               '+FDD\\r\\n', '+FAR\\r\\n', '+FCL\\r\\n', '+FIT\\r\\n', '+ES\\r\\n', '+ESA\\r\\n', '+CMOD\\r\\n', '+CVHU\\r\\n',\n                               '+CSQ\\r\\n', '+ZRSSI\\r\\n', '+CBC\\r\\n', '+CPAS\\r\\n', '+CPIN\\r\\n', '+CMEC\\r\\n', '+CKPD\\r\\n',\n                               '+CGATT\\r\\n', '+CGACT\\r\\n', '+CGCMOD\\r\\n', '+CPBS\\r\\n', '+CPBR\\r\\n', '+ZCPBR\\r\\n',\n                               '+ZUSIM\\r\\n', '+CPBF\\r\\n', '+CPBW\\r\\n', '+ZCPBW\\r\\n', '+CPMS\\r\\n', '+CNMI\\r\\n',\n                               '+CMGL\\r\\n', '+CMGR\\r\\n', '+CMGS\\r\\n', '+CMSS\\r\\n', '+CMGW\\r\\n', '+CMGD\\r\\n', '+CMGC\\r\\n',\n                               '+CNMA\\r\\n', '+CMMS\\r\\n', '+CHUP\\r\\n', '+CCFC\\r\\n', '+CCUG\\r\\n', '+COPS\\r\\n', '+CLCK\\r\\n',\n                               '+CPWD\\r\\n', '+CUSD\\r\\n', '+CAOC\\r\\n', '+CACM\\r\\n', '+CAMM\\r\\n', '+CPUC\\r\\n', '+CCWA\\r\\n',\n                               '+CHLD\\r\\n', '+CIMI\\r\\n', '+CGMI\\r\\n', '+CGMM\\r\\n', '+CGMR\\r\\n', '+CGSN\\r\\n', '+CNUM\\r\\n',\n                               '+CSIM\\r\\n', '+CRSM\\r\\n', '+CCLK\\r\\n', '+CLVL\\r\\n', '+CMUT\\r\\n', '+CLCC\\r\\n', '+COPN\\r\\n',\n                               '+CPOL\\r\\n', '+CPLS\\r\\n', '+CTZR\\r\\n', '+CTZU\\r\\n', '+CLAC\\r\\n', '+CLIP\\r\\n', '+COLP\\r\\n',\n                               '+CDIP\\r\\n', '+CTFR\\r\\n', '+CLIR\\r\\n', '$QCSIMSTAT\\r\\n', '$QCCNMI\\r\\n', '$QCCLR\\r\\n',\n                               '$QCDMG\\r\\n', '$QCDMR\\r\\n', '$QCDNSP\\r\\n', '$QCDNSS\\r\\n', '$QCTER\\r\\n', '$QCSLOT\\r\\n',\n                               '$QCPINSTAT\\r\\n', '$QCPDPP\\r\\n', '$QCPDPLT\\r\\n', '$QCPWRDN\\r\\n', '$QCDGEN\\r\\n',\n                               '$BREW\\r\\n', '$QCSYSMODE\\r\\n', 'OK\\r\\n'],\n                 'AT+WIND?\\r': ['ERROR\\r\\n'],\n                 'AT+WIND=50\\r': ['ERROR\\r\\n'],\n                 'AT+ZPAS?\\r':  ['+BEARTYPE: \"UMTS\",\"CS_PS\"\\r\\n', 'OK\\r\\n'],\n                 'AT+CPMS=?\\r': ['+CPMS: (\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\")\\r\\n', 'OK\\r\\n'],\n                 'AT+CVHU=0\\r': ['+CVHU: (0-1)\\r\\n', 'OK\\r\\n'],\n                 'AT+CPIN?\\r': ['+CPIN: READY\\r\\n', 'OK\\r\\n']}\n\n    def getResponse(self, cmd):\n        if type(cmd) == bytes:\n            cmd = cmd.decode()\n\n        if not self._pinLock:\n            if cmd.startswith('AT+CSMP='):\n                # Clear the SMSC number (this behaviour was reported in issue #8 on github)\n                self.smscNumber = None\n            elif cmd == 'AT+CLCC\\r':\n                if self._callNumber:\n                    if self._callState == 0:\n                        return ['+CLCC: 1,0,2,0,0,\"{0}\",129\\r\\n'.format(self._callNumber), 'OK\\r\\n']\n                    elif self._callState == 1:\n                        return ['+CLCC: 1,0,0,0,0,\"{0}\",129\\r\\n'.format(self._callNumber), 'OK\\r\\n']\n                    else:\n                        return ['OK\\r\\n']\n            return super(ZteK3565Z, self).getResponse(cmd)\n        else:\n            return super(ZteK3565Z, self).getResponse(cmd)\n\n    def getAtdResponse(self, number):\n        self._callNumber = number\n        self._callState = 0\n        return []\n\n    def getPreCallInitWaitSequence(self):\n        return [0.1]\n\n    def getCallInitNotification(self, callId, callType):\n        return []\n\n    def getRemoteAnsweredNotification(self, callId, callType):\n        return ['CONNECT\\r\\n']\n\n    def getRemoteHangupNotification(self, callId, callType):\n        self._callState = 2\n        self._callNumber = None\n        return ['HANGUP: {0}\\r\\n'.format(callId)]\n\n    def getRemoteRejectCallNotification(self, callId, callType):\n        self._callState = 2\n        self._callNumber = None\n        return [\"OK\\r\\n\"]\n\n    def getIncomingCallNotification(self, callerNumber, callType='VOICE', ton=145):\n        return ['+CRING: {0}\\r\\n'.format(callType), '+CLIP: \"{1}\",{2},,,,0\\r\\n'.format(callType, callerNumber, ton)]\n\n    def __str__(self):\n        return 'ZTE K3565-Z'\n\n\nclass NokiaN79(GenericTestModem):\n    \"\"\" Nokia Symbian S60-based modem (details taken from a Nokia N79) and\n    also from issue 15: https://github.com/faucamp/python-gsmmodem/issues/15 (Nokia N95)\n\n    SMS reading is not supported on these devices via AT commands; thus\n    commands like AT+CNMI are not supported.\n    \"\"\"\n\n    def __init__(self):\n        super(NokiaN79, self).__init__()\n        self.responses = {'AT+CGMI\\r': ['Nokia\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMM\\r': ['Nokia N79\\r\\n', 'OK\\r\\n'],\n                 'AT+CGMR\\r': ['V ICPR72_08w44.1\\r\\n', '24-11-08\\r\\n', 'RM-348\\r\\n', '(c) Nokia\\r\\n', '11.049\\r\\n', 'OK\\r\\n'],\n                 'AT+CIMI\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CGSN\\r': ['111111111111111\\r\\n', 'OK\\r\\n'],\n                 'AT+CNMI=?\\r': ['ERROR\\r\\n'], # SMS reading and notifications not supported\n                 'AT+CNMI=2,1,0,2\\r': ['ERROR\\r\\n'], # SMS reading and notifications not supported\n                 'AT+CLAC=?\\r': ['ERROR\\r\\n'],\n                 'AT+CLAC\\r': ['ERROR\\r\\n'],\n                 'AT+WIND=?\\r': ['ERROR\\r\\n'],\n                 'AT+WIND?\\r': ['ERROR\\r\\n'],\n                 'AT+WIND=50\\r': ['ERROR\\r\\n'],\n                 'AT+ZPAS=?\\r': ['ERROR\\r\\n'],\n                 'AT+ZPAS?\\r': ['ERROR\\r\\n'],\n                 'AT+CPMS=\"SM\",\"SM\",\"SR\"\\r': ['ERROR\\r\\n'],\n                 'AT+CPMS=?\\r': ['+CPMS: (),(),()\\r\\n', 'OK\\r\\n'], # not supported\n                 'AT+CPMS?\\r': ['+CPMS: ,,,,,,,,\\r\\n', 'OK\\r\\n'], # not supported\n                 'AT+CPMS=,,\\r': ['ERROR\\r\\n'],\n                 'AT+CPMS=\"SM\",\"SM\"\\r': ['ERROR\\r\\n'], # not supported\n                 'AT+CSMP?\\r': ['+CSMP: 49,167,0,0\\r\\n', 'OK\\r\\n'],\n                 'AT+GCAP\\r': ['+GCAP: +CGSM,+DS,+W\\r\\n', 'OK\\r\\n'],\n                 'AT+CNMI=2,1,0,2\\r': ['ERROR\\r\\n'], # not supported\n                 'AT+CVHU=0\\r': ['OK\\r\\n'],\n                 'AT+CPIN?\\r': ['+CPIN: READY\\r\\n', 'OK\\r\\n']}\n        self.commandsNoPinRequired = ['ATZ\\r', 'ATE0\\r', 'AT+CFUN?\\r', 'AT+CFUN=1\\r', 'AT+CMEE=1\\r']\n\n    def __str__(self):\n        return 'Nokia N79'\n\n\nmodemClasses = [HuaweiK3715, HuaweiE1752, WavecomMultiband900E1800, QualcommM6280, ZteK3565Z, NokiaN79]\n\n\ndef createModems():\n    return [modem() for modem in modemClasses]\n"
  },
  {
    "path": "test/test_gsmterm.py",
    "content": "#!/usr/bin/env python\n\n\"\"\" Test suite for GsmTerm \"\"\"\n\nimport sys, unittest\n\nfrom . import compat # For Python 2.6 compatibility\ntry:\n    import gsmtermlib.trie\nexcept ImportError:\n    # \"python -m unittest discover\" run from project root\n    sys.path.insert(0, 'tools')\n    import gsmtermlib.trie\n\nclass TestTrie(unittest.TestCase):\n    \"\"\" Tests the trie implementation used by GsmTerm \"\"\"    \n    \n    def setUp(self):        \n        self.trie = gsmtermlib.trie.Trie()\n        self.keyValuePairs = (('abc', 'def'),\n                         ('hallo', 'daar'),\n                         ('hoe gaan', 'dit met jou'),\n                         ('sbzd', '123'),\n                         ('abcde', '234627sdg'),\n                         ('ab', 'asdk;jgdjsagkl'))\n    \n    def test_storeSingle(self):\n        \"\"\" Tests single key/value pair storage \"\"\"        \n        self.trie['hallo'] = 'daar'        \n        self.assertEqual(self.trie['hallo'], 'daar')\n        self.assertEqual(len(self.trie), 1)\n        self.assertRaises(KeyError, self.trie.__getitem__, 'abc')\n        # Set/get None key\n        self.assertRaises(ValueError, self.trie.__setitem__, None, 'someValue')        \n        self.assertRaises(ValueError, self.trie.__getitem__, None)\n        # Store zero-length key\n        self.assertRaises(KeyError, self.trie.__getitem__, '')\n        self.trie[''] = '123'\n        self.assertEqual(self.trie[''], '123')\n        self.assertEqual(len(self.trie), 2)\n    \n    def test_deleteSingle(self):\n        \"\"\" Tests deleting single key/value pair \"\"\"        \n        self.trie['hallo'] = 'daar'        \n        self.assertEqual(self.trie['hallo'], 'daar')\n        self.assertEqual(len(self.trie), 1)\n        del self.trie['hallo']\n        self.assertRaises(KeyError, self.trie.__getitem__, 'hallo')\n        self.assertEqual(len(self.trie), 0)\n        # Delete None key\n        self.assertRaises(ValueError, self.trie.__delitem__, None)\n        # Delete unknown key\n        self.assertRaises(KeyError, self.trie.__delitem__, 'unknown key')\n        # Delete zero-length unknown key\n        self.assertRaises(KeyError, self.trie.__delitem__, '')\n        # Delete zero-lenght known key\n        self.trie[''] = '123'\n        self.assertEqual(len(self.trie), 1)\n        del self.trie['']\n        self.assertEqual(len(self.trie), 0)\n    \n    def test_storeRetrieveMultiple(self):\n        n = 0\n        for key, value in self.keyValuePairs:\n            n += 1\n            self.trie[key] = value\n            self.assertEqual(self.trie[key], value)            \n            # Make sure nothing was lost\n            for oldKey, oldValue in self.keyValuePairs[:n-1]:\n                self.assertEqual(self.trie[oldKey], oldValue)\n                \n    def test_storeDeleteMultiple(self):\n        self.assertEqual(len(self.trie), 0)\n        for key, value in self.keyValuePairs:\n            self.trie[key] = value\n        self.assertEqual(len(self.trie), len(self.keyValuePairs))\n        n = len(self.trie)\n        for key, value in self.keyValuePairs:\n            n -= 1\n            del self.trie[key]\n            self.assertEqual(len(self.trie), n)\n        self.assertEqual(len(self.trie), 0)\n\n    def test_len(self):\n        n = 0\n        for key, value in self.keyValuePairs:\n            n += 1\n            self.trie[key] = value\n            self.assertEqual(len(self.trie), n, 'Incorrect trie length. Expected {0}, got {1}. Last entry: {2}: {3}'.format(n, len(self.trie), key, value))\n    \n    def test_contains(self):\n        for key, value in self.keyValuePairs:\n            self.assertFalse(key in self.trie)\n            self.trie[key] = value\n            self.assertTrue(key in self.trie)\n            \n    def test_getMethod(self):\n        # Test invalid None key\n        self.assertRaises(ValueError, self.trie.get, None)\n        # Test unknown key\n        self.assertEqual(self.trie.get('abc'), None) # no default\n        self.assertEqual(self.trie.get('abc', default='def'), 'def') # with default\n        self.trie['abc'] = '123'\n        self.assertEqual(self.trie.get('abc'), '123') # no default\n        self.assertEqual(self.trie.get('abc', default='def'), '123') # with default \n\n    def test_keys(self):\n        \"\"\" Test the \"keys\" method of the trie \"\"\"\n        localKeys = []\n        for key, value in self.keyValuePairs:\n            localKeys.append(key)\n            self.trie[key] = value\n        # The trie has no concept of ordering, so we can't simply compare keys with ==\n        trieKeys = self.trie.keys()\n        self.assertEqual(len(trieKeys), len(localKeys))\n        for key in localKeys:\n            self.assertTrue(key in trieKeys)\n    \n    def test_overWrite(self):\n        # Fill up trie with some values\n        for key, value in self.keyValuePairs:            \n            self.trie[key] = value\n        key, oldValue = self.keyValuePairs[0]\n        length = len(self.keyValuePairs)\n        self.assertEqual(self.trie[key], oldValue)\n        self.assertEqual(len(self.trie), length)\n        # Overwrite value\n        newValue = oldValue + '12345'\n        self.assertNotEqual(oldValue, newValue)\n        self.trie[key] = newValue\n        # Read it back\n        self.assertEqual(self.trie[key], newValue)\n        # Check trie length is unchanged\n        self.assertEqual(len(self.trie), length)\n    \n    def test_filteredKeys(self):\n        \"\"\" Test the \"matching keys\" functionality of the trie \"\"\"\n        keys = ('a', 'ab', 'abc', 'abcd0000', 'abcd1111', 'abcd2222', 'abcd3333', 'b000', 'b1111', 'zzz123', 'zzzz1234', 'xyz123', 'AT+CSCS')\n        prefixMatches = (('abc', [key for key in keys if key.startswith('abc')]),\n                         ('b', [key for key in keys if key.startswith('b')]),\n                         ('bc', [key for key in keys if key.startswith('bc')]),\n                         ('zzz', [key for key in keys if key.startswith('zzz')]),\n                         ('x', [key for key in keys if key.startswith('x')]),\n                         ('xy', [key for key in keys if key.startswith('xy')]),\n                         ('qwerty', [key for key in keys if key.startswith('qwerty')]),\n                         ('AT+CSCS=', [key for key in keys if key.startswith('AT+CSCS=')]))\n\n        for key in keys:\n            self.trie[key] = 1\n        for prefix, matchingKeys in prefixMatches:\n            trieKeys = self.trie.keys(prefix)\n            self.assertEqual(len(trieKeys), len(matchingKeys), 'Filtered keys length failed. Prefix: {0}, expected len: {1}, items: {2}, got len {3}, items: {4}'.format(prefix, len(matchingKeys), matchingKeys, len(trieKeys), trieKeys))\n            for key in matchingKeys:\n                self.assertTrue(key in trieKeys, 'Key not in trie keys: {0}. Trie keys: {1}'.format(key, trieKeys))\n    \n    def test_longestCommonPrefix(self):\n        \"\"\" Test the \"get longest common prefix\" functionality of the trie \"\"\"\n        keys = ('abcDEF', 'abc123', 'abcASFDDSFDSF', 'abc@#$@#$', 'abcDxxx')        \n        for key in keys:\n            self.trie[key] = 1        \n        self.assertEqual(self.trie.longestCommonPrefix(), 'abc')\n        self.assertEqual(self.trie.longestCommonPrefix('a'), 'abc')\n        self.assertEqual(self.trie.longestCommonPrefix('ab'), 'abc')\n        self.assertEqual(self.trie.longestCommonPrefix('abc'), 'abc')\n        self.assertEqual(self.trie.longestCommonPrefix('abcD'), 'abcD')\n        self.assertEqual(self.trie.longestCommonPrefix('abcDz'), '')\n        self.assertEqual(self.trie.longestCommonPrefix('abcDE'), 'abcDEF')\n        self.assertEqual(self.trie.longestCommonPrefix('abcDEF'), 'abcDEF')\n        self.assertEqual(self.trie.longestCommonPrefix('abcDEz'), '')\n        keys = ('ATD', 'ATDL')\n        for key in keys:\n            self.trie[key] = 1\n        self.assertEqual(self.trie.longestCommonPrefix(), '')\n        self.assertEqual(self.trie.longestCommonPrefix('A'), 'ATD')\n        self.assertEqual(self.trie.longestCommonPrefix('AT'), 'ATD')\n        self.assertEqual(self.trie.longestCommonPrefix('ATD'), 'ATD')\n    \n    def test_iter(self):\n        localKeys = []\n        for key, value in self.keyValuePairs:\n            localKeys.append(key)\n            self.trie[key] = value\n        n = 0\n        a = iter(self.trie)\n        while True:\n            try:\n                self.assertIn(next(a), localKeys)\n            except StopIteration:\n                break\n            else:\n                n += 1\n        self.assertEqual(n, len(self.trie))\n\n\nclass TestAtCommands(unittest.TestCase):\n    \"\"\" Test suite for the AT Commands data structure \"\"\"\n    \n    def test_loadAtCommands(self):\n        \"\"\" Check that the AT commands can be loaded correctly, and they are correctly formatted \"\"\"\n        from gsmtermlib.atcommands import ATCOMMANDS, CATEGORIES\n        for command, help in ATCOMMANDS:\n            self.assertNotEqual(command, None)\n            self.assertGreater(len(command), 0)\n            self.assertEqual(command.strip(), command, 'Command has leading and/or trailing spaces: {0}'.format(command))\n            \n            self.assertNotEqual(help, None, 'Command\\'s help tuple is None: {0}'.format(command))\n            self.assertGreaterEqual(len(help), 2)\n            self.assertTrue(help[0] in CATEGORIES)\n            if len(help) > 2:\n                if help[2] != None:\n                    self.assertIsInstance(help[2], tuple)\n                    self.assertGreater(len(help[2]), 0)\n                    for item in help[2]:\n                        self.assertEqual(len(item), 2, 'Input value item tuple length should be 2, got {0}. Command: {1}, item: {2}'.format(len(item), command, item))\n                if help[3] != None:\n                    self.assertIsInstance(help[3], tuple)\n                    self.assertGreater(len(help[3]), 0)\n                    for item in help[3]:\n                        self.assertEqual(len(item), 2, 'Output value item tuple length should be 2, got {0}. Command: {1}, item: {2}'.format(len(item), command, item))\n                self.assertIsInstance(help[4], str)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_modem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf8 -*-\n\n\"\"\" Test suite for gsmmodem.modem \"\"\"\n\nfrom __future__ import print_function\n\nimport sys, time, unittest, logging, codecs\nfrom datetime import datetime\nfrom copy import copy\n\nfrom . import compat # For Python 2.6 compatibility\nfrom gsmmodem.exceptions import PinRequiredError, CommandError, InvalidStateException, TimeoutException,\\\n    CmsError, CmeError, EncodingError\nfrom gsmmodem.modem import StatusReport, Sms, ReceivedSms\n\nPYTHON_VERSION = sys.version_info[0]\n\nimport gsmmodem.serial_comms\nimport gsmmodem.modem\nimport gsmmodem.pdu\nfrom gsmmodem.util import SimpleOffsetTzInfo\n\nfrom . import fakemodems\n\n# Silence logging exceptions\nlogging.raiseExceptions = False\nif sys.version_info[0] == 3 and sys.version_info[1] >= 1:\n    logging.getLogger('gsmmodem').addHandler(logging.NullHandler())\n\n# The fake modem to use (if any)\nFAKE_MODEM = None\n# Write callback to use during Serial.__init__() - usually None, but useful for setting write callbacks during modem.connect()\nSERIAL_WRITE_CALLBACK_FUNC = None\n\nclass MockSerialPackage(object):\n    \"\"\" Fake serial package for the GsmModem/SerialComms classes to import during tests \"\"\"\n    \n    class Serial():\n        \n        _REPONSE_TIME = 0.02\n        \n        \"\"\" Mock serial object for use by the GsmModem class during tests \"\"\"\n        def __init__(self, *args, **kwargs):\n            # The default value to read/\"return\" if responseSequence isn't set up, or None for nothing\n            #self.defaultResponse = 'OK\\r\\n'\n            self.responseSequence = []\n            self.flushResponseSequence = True\n            self.writeQueue = []\n            self._alive = True\n            self._readQueue = []\n            global SERIAL_WRITE_CALLBACK_FUNC\n            self.writeCallbackFunc = SERIAL_WRITE_CALLBACK_FUNC\n            global FAKE_MODEM\n            # Pre-determined responses to specific commands - used for imitating specific modems\n            if FAKE_MODEM != None:\n                self.modem = copy(FAKE_MODEM)\n            else:\n                self.modem = fakemodems.GenericTestModem()\n        \n        def read(self, timeout=None):\n            if len(self._readQueue) > 0:    \n                return self._readQueue.pop(0)                        \n            elif len(self.writeQueue) > 0:  \n                self._setupReadValue(self.writeQueue.pop(0))\n                if len(self._readQueue) > 0:\n                    return self._readQueue.pop(0)\n            elif self.flushResponseSequence and len(self.responseSequence) > 0:\n                self._setupReadValue(None)\n            \n            if timeout != None:\n                time.sleep(0.001)\n                return ''\n            else:\n                while self._alive:\n                    if len(self.writeQueue) > 0:\n                        self._setupReadValue(self.writeQueue.pop(0))\n                        if len(self._readQueue) > 0:\n                            return self._readQueue.pop(0)\n                    time.sleep(0.05)\n                    \n        def _setupReadValue(self, command):\n            if len(self._readQueue) == 0:\n                if len(self.responseSequence) > 0:\n                    value = self.responseSequence.pop(0)    \n                    if type(value) in (float, int):\n                        time.sleep(value)                        \n                        if len(self.responseSequence) > 0:                            \n                            self._setupReadValue(command)                    \n                    else:                        \n                        self._readQueue = list(value)\n                else:\n                    self.responseSequence = self.modem.getResponse(command)\n                    if len(self.responseSequence) > 0:\n                        self._setupReadValue(command)\n                #elif command in self.modem.responses:\n                #    self.responseSequence = self.modem.responses[command]\n                #    if len(self.responseSequence) > 0:\n                #        self._setupReadValue(command)\n                #elif self.defaultResponse != None:\n                #    self._readQueue = list(self.defaultResponse)\n                \n        def write(self, data):            \n            if self.writeCallbackFunc != None:\n                if type(data) == bytes:\n                    data = data.decode()\n                self.writeCallbackFunc(data)\n            self.writeQueue.append(data)\n            \n        def close(self):\n            pass\n            \n        def inWaiting(self):\n            rqLen = len(self._readQueue)\n            for item in self.responseSequence:\n                if type(item) in (int, float):\n                    break\n                else:\n                    rqLen += len(item)\n            return rqLen\n            \n    \n    class SerialException(Exception):\n        \"\"\" Mock Serial Exception \"\"\"\n\n\nclass TestGsmModemGeneralApi(unittest.TestCase):\n    \"\"\" Tests the API of GsmModem class (excluding connect/close) \"\"\"\n    \n    def setUp(self):\n        # Override the pyserial import        \n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n        self.modem.connect()\n    \n    def tearDown(self):\n        self.modem.close()\n        \n    def test_manufacturer(self):\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CGMI\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CGMI\\r', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = ['huawei', 'ABCDefgh1235', 'Some Random Manufacturer']\n        for test in tests:\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(test), 'OK\\r\\n']            \n            self.assertEqual(test, self.modem.manufacturer)\n    \n    def test_model(self):\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CGMM\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CGMM\\r', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = ['K3715', '1324-Qwerty', 'Some Random Model']\n        for test in tests:\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(test), 'OK\\r\\n']            \n            self.assertEqual(test, self.modem.model)\n            \n    def test_revision(self):\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CGMR\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CGMR\\r', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = ['1', '1324-56768-23414', 'r987']\n        for test in tests:\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(test), 'OK\\r\\n']\n            self.assertEqual(test, self.modem.revision)\n        # Fake a modem that does not support this command\n        self.modem.serial.modem.defaultResponse = ['ERROR\\r\\n']\n        self.assertEqual(None, self.modem.revision)\n    \n    def test_imei(self):\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CGSN\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CGSN\\r', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = ['012345678912345']\n        for test in tests:\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(test), 'OK\\r\\n']            \n            self.assertEqual(test, self.modem.imei)\n            \n    def test_imsi(self):\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CIMI\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CIMI\\r', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = ['987654321012345']\n        for test in tests:\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(test), 'OK\\r\\n']\n            self.assertEqual(test, self.modem.imsi)\n\n    def test_networkName(self):\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+COPS?\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+COPS', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = [('MTN', '+COPS: 0,0,\"MTN\",2'),\n                 ('I OMNITEL', '+COPS: 0,0,\"I OMNITEL\"'),\n                 (None, 'SOME RANDOM RESPONSE')]\n        for name, toWrite in tests:\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(toWrite), 'OK\\r\\n']\n            self.assertEqual(name, self.modem.networkName)\n\n    def test_supportedCommands(self):\n        def writeCallbackFunc(data):\n            if data == 'AT\\r': # Handle keep-alive AT command\n                return\n            self.assertEqual('AT+CLAC\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CLAC\\r', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = ((['+CLAC:&C,D,E,\\S,+CGMM,^DTMF\\r\\n', 'OK\\r\\n'], ['&C', 'D', 'E', '\\S', '+CGMM', '^DTMF']),\n                 (['+CLAC:Z\\r\\n', 'OK\\r\\n'], ['Z']),\n                 (['FGH,RTY,UIO\\r\\n', 'OK\\r\\n'], ['FGH', 'RTY', 'UIO']), # nasty, but possible\n                 # ZTE-like response: do not start with +CLAC, and use multiple lines\n                 (['A\\r\\n', 'BCD\\r\\n', 'EFGH\\r\\n', 'OK\\r\\n'], ['A', 'BCD', 'EFGH']),\n                 # Teleofis response: like ZTE but each command has AT prefix\n                 (['AT&F\\r\\n', 'AT&V\\r\\n', 'AT&W\\r\\n', 'AT+CACM\\r\\n', 'OK\\r\\n'], ['&F', '&V', '&W', '+CACM']),\n                 # Some Huawei modems have a ZTE-like response, but add an addition \\r character at the end of each listed command\n                 (['Q\\r\\r\\n', 'QWERTY\\r\\r\\n', '^DTMF\\r\\r\\n', 'OK\\r\\n'], ['Q', 'QWERTY', '^DTMF']))\n        for responseSequence, expected in tests:\n            self.modem.serial.responseSequence = responseSequence\n            commands = self.modem.supportedCommands\n            self.assertEqual(commands, expected)\n        # Fake a modem that does not support this command\n        self.modem.serial.responseSequence = ['ERROR\\r\\n']\n        commands = self.modem.supportedCommands\n        self.assertEqual(commands, None)\n        # Test unhandled response format\n        self.modem.serial.responseSequence = ['OK\\r\\n']\n        commands = self.modem.supportedCommands\n        self.assertEqual(commands, None)\n\n    def test_smsc(self):\n        \"\"\" Tests reading and writing the SMSC number from the SIM card \"\"\"\n        def writeCallbackFunc1(data):\n            self.assertEqual('AT+CSCA?\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCA?', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc1\n        tests = [None, '+12345678']\n        for test in tests:\n            if test:\n                self.modem.serial.responseSequence = ['+CSCA: \"{0}\",145\\r\\n'.format(test), 'OK\\r\\n']\n            else:\n                self.modem.serial.responseSequence = ['OK\\r\\n']\n            self.assertEqual(test, self.modem.smsc)\n        # Reset SMSC number internally\n        self.modem._smscNumber = None\n        self.assertEqual(self.modem.smsc, None)\n        # Now test setting the SMSC number\n        for test in tests:\n            if not test:\n                continue\n            def writeCallbackFunc2(data):\n                self.assertEqual('AT+CSCA=\"{0}\"\\r'.format(test), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCA=\"{0}\"'.format(test), data))\n            def writeCallbackFunc3(data):\n                # This method should not be called - it merely exists to make sure nothing is written to the modem\n                self.fail(\"Nothing should have been written to modem, but got: {0}\".format(data))\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n            self.modem.smsc = test\n            self.assertEqual(test, self.modem.smsc)\n            # Now see if the SMSC value was cached properly\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n            self.assertEqual(test, self.modem.smsc)\n            self.modem.smsc = test # Shouldn't do anything\n        # Check response if modem returns a +CMS ERROR: 330 (SMSC number unknown) on querying the SMSC\n        self.modem._smscNumber = None\n        self.modem.serial.responseSequence = ['+CMS ERROR: 330\\r\\n']\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc1\n        self.assertEqual(self.modem.smsc, None) # Should just return None\n    \n    def test_signalStrength(self):\n        \"\"\" Tests reading signal strength from the modem \"\"\"\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CSQ\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSQ', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = (('+CSQ: 18,99', 18),\n                 ('+CSQ:4,0', 4),\n                 ('+CSQ: 99,99', -1))\n        for response, expected in tests:\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(response), 'OK\\r\\n']\n            self.assertEqual(expected, self.modem.signalStrength)\n        # Test error condition (unparseable response)\n        self.modem.serial.responseSequence = ['OK\\r\\n']\n        try:\n            self.modem.signalStrength\n        except CommandError:\n            pass\n        else:\n            self.fail('CommandError not raised on error condition')\n\n    def test_waitForNetorkCoverageNoCreg(self):\n        \"\"\" Tests waiting for network coverage (no AT+CREG support) \"\"\"\n        tests = ((82,),\n                 (99, 99, 47),)\n        for seq in tests:\n            items = iter(seq)\n            def writeCallbackFunc(data):\n                if data == 'AT+CSQ\\r':\n                    try:\n                        self.modem.serial.responseSequence = ['+CSQ: {0},99\\r\\n'.format(next(items)), 'OK\\r\\n']\n                    except StopIteration:\n                        self.fail(\"Too many AT+CSQ writes issued\")                \n            self.modem.serial.writeCallbackFunc = writeCallbackFunc            \n            signalStrength = self.modem.waitForNetworkCoverage()\n            self.assertNotEqual(signalStrength, -1, '\"Unknown\" signal strength returned - should still have blocked')\n            self.assertEqual(seq[-1], signalStrength, 'Incorrect signal strength returned')\n\n    def test_waitForNetorkCoverage(self):\n        \"\"\" Tests waiting for network coverage (normal) \"\"\"\n        tests = (('0,2', '0,2', '0,1', 82),\n                 ('0,5', 47),)\n        for seq in tests:\n            items = iter(seq)\n            def writeCallbackFunc(data):\n                if data == 'AT+CSQ\\r':\n                    try:\n                        self.modem.serial.responseSequence = ['+CSQ: {0},99\\r\\n'.format(next(items)), 'OK\\r\\n']\n                    except StopIteration:\n                        self.fail(\"Too many writes issued\")\n                elif data == 'AT+CREG?\\r':\n                    try:\n                        self.modem.serial.responseSequence = ['+CREG: {0}\\r\\n'.format(next(items)), 'OK\\r\\n']\n                    except StopIteration:\n                        self.fail(\"Too many writes issued\")\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            signalStrength = self.modem.waitForNetworkCoverage()\n            self.assertNotEqual(signalStrength, -1, '\"Unknown\" signal strength returned - should still have blocked')\n            self.assertEqual(seq[-1], signalStrength, 'Incorrect signal strength returned')\n        # Test InvalidStateException\n        tests = ('0,3', '0,0') # 0,3: network registration denied. 0,0: SIM not searching for network\n        for result in tests:\n            def writeCallbackFunc(data):\n                if data == 'AT+CREG?\\r':\n                    self.modem.serial.responseSequence = ['+CREG: {0}\\r\\n'.format(result), 'OK\\r\\n']\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.assertRaises(InvalidStateException, self.modem.waitForNetworkCoverage)\n        # Test TimeoutException\n        def writeCallbackFunc2(data):\n            self.modem.serial.responseSequence = ['+CREG: 0,1\\r\\n'.format(result), 'OK\\r\\n']\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n        self.assertRaises(TimeoutException, self.modem.waitForNetworkCoverage, timeout=1)\n        \n    def test_errorTypes(self):\n        \"\"\" Tests error type detection- and handling by throwing random errors to commands \"\"\"\n        # Throw unnamed error\n        self.modem.serial.responseSequence = ['ERROR\\r\\n']\n        try:\n            self.modem.write('AT')\n        except CommandError as e:\n            self.assertIsInstance(e, CommandError)\n            self.assertEqual(e.command, 'AT')\n            self.assertEqual(e.type, None)\n            self.assertEqual(e.code, None)\n        # Throw CME error\n        self.modem.serial.responseSequence = ['+CME ERROR: 22\\r\\n']\n        try:\n            self.modem.write('AT+ZZZ')\n        except CommandError as e:\n            self.assertIsInstance(e, CmeError)\n            self.assertEqual(e.command, 'AT+ZZZ')\n            self.assertEqual(e.type, 'CME')\n            self.assertEqual(e.code, 22)\n        # Throw CMS error\n        self.modem.serial.responseSequence = ['+CMS ERROR: 310\\r\\n']\n        try:\n            self.modem.write('AT+XYZ')\n        except CommandError as e:\n            self.assertIsInstance(e, CmsError)\n            self.assertEqual(e.command, 'AT+XYZ')\n            self.assertEqual(e.type, 'CMS')\n            self.assertEqual(e.code, 310)\n\n    def test_smsEncoding(self):\n        def writeCallbackFunc(data):\n            if type(data) == bytes:\n                data = data.decode()\n            self.assertEqual('AT+CSCS?\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS?', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = [('UNKNOWN', '+CSCSERROR'),\n                 ('UCS2', '+CSCS: \"UCS2\"'),\n                 ('ISO', '+CSCS:\"ISO\"'),\n                 ('IRA', '+CSCS: \"IRA\"'),\n                 ('UNKNOWN', '+CSCS: (\"GSM\", \"UCS2\")'),\n                 ('UNKNOWN', 'OK'),]\n        for name, toWrite in tests:\n            self.modem._smsEncoding = 'UNKNOWN'\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(toWrite), 'OK\\r\\n']\n            self.assertEqual(name, self.modem.smsEncoding)\n\n    def test_smsSupportedEncoding(self):\n        def writeCallbackFunc(data):\n            if type(data) == bytes:\n                data = data.decode()\n            self.assertEqual('AT+CSCS=?\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS?', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        tests = [(['GSM'], '+CSCS: (\"GSM\")'),\n                 (['GSM', 'UCS2'], '+CSCS: (\"GSM\", \"UCS2\")'),\n                 (['GSM', 'UCS2'], '+CSCS:(\"GSM\",\"UCS2\")'),\n                 (['GSM', 'UCS2'], '+CSCS:   (  \"GSM\"  ,   \"UCS2\"  )'),\n                 (['GSM'], '+CSCS: (\"GSM\" \"UCS2\")'),]\n        for name, toWrite in tests:\n            self.modem._smsSupportedEncodingNames = None\n            self.modem.serial.responseSequence = ['{0}\\r\\n'.format(toWrite), 'OK\\r\\n']\n            self.assertEqual(name, self.modem.smsSupportedEncoding)\n\n\nclass TestUssd(unittest.TestCase):\n    \"\"\" Tests USSD session handling \"\"\"\n\n    def setUp(self):\n        #logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n        self.tests = tests = [('*101#', 'AT+CUSD=1,\"*101#\",15\\r', '+CUSD: 0,\"Available Balance: R 96.45 .\",15\\r\\n', 'Available Balance: R 96.45 .', False),\n                 ('*120*500#', 'AT+CUSD=1,\"*120*500#\",15\\r', '+CUSD: 1,\"Hallo daar\",15\\r\\n', 'Hallo daar', True),\n                 ('*130*111#', 'AT+CUSD=1,\"*130*111#\",15\\r', '+CUSD: 2,\"Totsiens\",15\\r\\n', 'Totsiens', False),\n                 ('*111*502#', 'AT+CUSD=1,\"*111*502#\",15\\r', '+CUSD: 2,\"You have the following remaining balances:\\n0 free minutes\\n20 MORE Weekend minutes \",15\\r\\n', 'You have the following remaining balances:\\n0 free minutes\\n20 MORE Weekend minutes ', False),\n                 ('#100#', 'AT+CUSD=1,\"#100#\",15\\r', '+CUSD: 1,\"Bal:$100.00 *\\r\\nExp 01 Jan 2013\\r\\n1. Recharge\\r\\n2. Balance\\r\\n3. My Offer\\r\\n4. PlusPacks\\r\\n5. Tones&Extras\\r\\n6. History\\r\\n7. CredMe2U\\r\\n8. Hlp\\r\\n00. Home\\r\\n*charges can take 48hrs\",15\\r\\n', \n                  'Bal:$100.00 *\\r\\nExp 01 Jan 2013\\r\\n1. Recharge\\r\\n2. Balance\\r\\n3. My Offer\\r\\n4. PlusPacks\\r\\n5. Tones&Extras\\r\\n6. History\\r\\n7. CredMe2U\\r\\n8. Hlp\\r\\n00. Home\\r\\n*charges can take 48hrs', True)]\n        # Override the pyserial import\n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        self.modem.connect()\n\n    def tearDown(self):\n        self.modem.close()\n\n    def test_sendUssd(self):\n        \"\"\" Standard USSD tests \"\"\"\n        # tests tuple format: (USSD_STRING_TO_WRITE, MODEM_WRITE, MODEM_RESPONSE, USSD_MESSAGE, USSD_SESSION_ACTIVE)\n        for test in self.tests:\n            def writeCallbackFunc(data):\n                self.assertEqual(test[1], data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format(test[1], data))\n            self.modem.serial.responseSequence = ['OK\\r\\n', test[2]]\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            ussd = self.modem.sendUssd(test[0])\n            self.assertIsInstance(ussd, gsmmodem.modem.Ussd)\n            self.assertEqual(ussd.sessionActive, test[4], 'Session state is invalid for test case: {0}'.format(test))\n            self.assertEqual(ussd.message, test[3])\n            if ussd.sessionActive:\n                def writeCallbackFunc2(data):\n                    self.assertEqual('AT+CUSD=2\\r', data, 'Invalid data written to modem; expected \"AT+CUSD=2\", got: \"{0}\"'.format(data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                ussd.cancel()\n            else:\n                ussd.cancel() # This call shouldn't do anything\n            del ussd\n            \n    def test_sendUssd_differentModems(self):\n        \"\"\" Tests sendUssd functionality with different modem behaviours (some modems require mode switching) \"\"\"\n        tests = [('*101#', 'Testing 123')]\n        global FAKE_MODEM\n        for ussdStr, ussdResponse in tests:\n            for fakeModem in fakemodems.createModems():\n                fakeModem.responses['AT+CUSD=1,\"{0}\",15\\r'.format(ussdStr)] = ['+CUSD: 2,\"{0}\",15\\r\\n'.format(ussdResponse), 'OK\\r\\n']\n                # Init modem and preload SMSC number\n                FAKE_MODEM = fakeModem\n                mockSerial = MockSerialPackage()\n                gsmmodem.serial_comms.serial = mockSerial        \n                modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n                modem.connect()\n                response = modem.sendUssd(ussdStr)\n                self.assertEqual(ussdResponse, response.message)\n                modem.close()\n        FAKE_MODEM = None\n    \n    def test_sendUssdReply(self):\n        \"\"\" Test replying in a USSD session via Ussd.reply() \"\"\"\n        test = ('First menu. Reply with 1 for blah blah blah...', 'Second menu')\n        self.modem.serial.responseSequence = ['+CUSD: 1,\"{0}\",15\\r\\n'.format(test[0]), 'OK\\r\\n']\n        ussd = self.modem.sendUssd('*101#')\n        self.assertIsInstance(ussd, gsmmodem.modem.Ussd)\n        self.assertTrue(ussd.sessionActive, 'Session should be active')\n        self.assertEqual(ussd.message, test[0])\n        # Reply to this active session\n        self.modem.serial.responseSequence = ['+CUSD: 2,\"{0}\",15\\r\\n'.format(test[1]), 'OK\\r\\n']\n        ussd = ussd.reply('1')\n        self.assertIsInstance(ussd, gsmmodem.modem.Ussd)\n        self.assertFalse(ussd.sessionActive, 'Session should be inactive')\n        self.assertEqual(ussd.message, test[1])\n        # Reply to inactive session\n        self.assertRaises(gsmmodem.exceptions.InvalidStateException, ussd.reply, '2')\n\n    def test_sendUssdResponseBeforeOk(self):\n        \"\"\" Tests +CUSD responses that arrive before the +CUSD command's OK is issued (non-standard behaviour) - reported by user \"\"\"\n        # tests tuple format: (USSD_STRING_TO_WRITE, MODEM_WRITE, MODEM_RESPONSE, USSD_MESSAGE, USSD_SESSION_ACTIVE)\n        for test in self.tests:\n            def writeCallbackFunc(data):\n                self.assertEqual(test[1], data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format(test[1], data))\n            # Note: The +CUSD response will now be sent before the command is acknowledged\n            self.modem.serial.responseSequence = [test[2], 'OK\\r\\n']\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            ussd = self.modem.sendUssd(test[0])\n            self.assertIsInstance(ussd, gsmmodem.modem.Ussd)\n            self.assertEqual(ussd.sessionActive, test[4], 'Session state is invalid for test case: {0}'.format(test))\n            self.assertEqual(ussd.message, test[3])\n            if ussd.sessionActive:\n                def writeCallbackFunc2(data):\n                    self.assertEqual('AT+CUSD=2\\r', data, 'Invalid data written to modem; expected \"AT+CUSD=2\", got: \"{0}\"'.format(data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                ussd.cancel()\n            else:\n                ussd.cancel() # This call shouldn't do anything\n            del ussd\n    \n    def test_sendUssdExtraRelease(self):\n        \"\"\" Some modems send an extra +CUSD: 2 message when the USSD session is released - see issue #14 on github \"\"\"\n        tests = (('*100#', 'Wrong order test message', ['+CUSD: 2,\"Initiating Release\",15\\r\\n', '+CUSD: 0,\"Wrong order test message\",15\\r\\n', 'OK\\r\\n']),\n                 ('*101#', 'Notifications test', ['OK\\r\\n', '+CUSD: 2,\"Initiating Release\",15\\r\\n', '+CUSD: 0,\"Notifications test\",15\\r\\n']),\n                 ('*101#', 'Test2', ['OK\\r\\n', '+CUSD: 3,\"Other local client responded\",15\\r\\n', '+CUSD: 0,\"Test2\",15\\r\\n']))\n        for test in tests:            \n            self.modem.serial.responseSequence = test[2]\n            ussd = self.modem.sendUssd(test[0])\n            self.assertIsInstance(ussd, gsmmodem.modem.Ussd)\n            self.assertEqual(ussd.message, test[1], 'Invalid message received; expected \"{0}\", got \"{1}\"'.format(test[1], ussd.message))\n            self.assertEqual(ussd.sessionActive, False, 'Invalid session state - should be inactive')\n            # Make sure the next call does not include any of the USSD extras\n            atResponse = self.modem.write('AT')\n            self.assertEqual(len(atResponse), 1)\n            self.assertEqual(atResponse[0], 'OK')\n    \n    def test_sendUssdError(self):\n        \"\"\" Test error handling in a USSD session \"\"\"\n        self.modem.serial.responseSequence = ['+CME ERROR: 30\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.CmeError, self.modem.sendUssd, '*101#')\n        self.modem.serial.responseSequence = ['+CMS ERROR: 500\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.CmsError, self.modem.sendUssd, '*101#')\n        self.modem.serial.responseSequence = ['ERROR\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.CommandError, self.modem.sendUssd, '*101#')\n        \n    def test_sendUssdExtraLinesInResponse(self):\n        \"\"\" Test parsing USSD response if it contains extra unsolicited notifications \"\"\"\n        tests = (('Notification appended', ['OK\\r\\n', 0.1, '+CUSD: 2,\"Notification appended\",15\\r\\n', 'Some random notification!\\r\\n']),\n                 ('Notification prepended', ['OK\\r\\n', 0.1, 'Another random notification!\\r\\n', '+CUSD: 2,\"Notification prepended\",15\\r\\n']),\n                 ('Notification before OK', ['Yet another random notification!\\r\\n', 'OK\\r\\n', 0.1, '+CUSD: 2,\"Notification before OK\",15\\r\\n']))\n        for message, responseSeq in tests:\n            self.modem.serial.responseSequence = responseSeq\n            ussd = self.modem.sendUssd('*101#')\n            self.assertIsInstance(ussd, gsmmodem.modem.Ussd)\n            self.assertEqual(ussd.message, message)\n    \n    def test_sendUssd_responseTimeout(self):\n        \"\"\" Test sendUssd() response timeout event \"\"\"\n        # The following should timeout very quickly due to no +CUSD update being issued\n        self.assertRaises(gsmmodem.exceptions.TimeoutException, self.modem.sendUssd, **{'ussdString': '*101#', 'responseTimeout': 0.05})\n\n\nclass TestEdgeCases(unittest.TestCase):\n    \"\"\" Edge-case testing; some modems do funny things during seemingly normal operations \"\"\"    \n\n    def test_smscPreloaded(self):\n        \"\"\" Tests reading the SMSC number if it was pre-loaded on the SIM (some modems delete the number during connect()) \"\"\"\n        tests = [None, '+12345678']\n        global FAKE_MODEM\n        for test in tests:\n            for fakeModem in fakemodems.createModems():\n                # Init modem and preload SMSC number\n                fakeModem.smscNumber = test\n                fakeModem.simBusyErrorCounter = 3 # Enable \"SIM busy\" errors for modem for more accurate testing\n                FAKE_MODEM = fakeModem\n                mockSerial = MockSerialPackage()\n                gsmmodem.serial_comms.serial = mockSerial        \n                modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n                modem.connect()\n                # Make sure SMSC number was prevented from being deleted (some modems do this when setting text-mode paramters AT+CSMP)\n                self.assertEqual(test, modem.smsc, 'SMSC number was changed/deleted during connect()')\n                modem.close()\n        FAKE_MODEM = None\n    \n    def test_cfun0(self):\n        \"\"\" Tests case where a modem's functionality setting is 0 at startup \"\"\"\n        global FAKE_MODEM\n        for fakeModem in fakemodems.createModems():\n            fakeModem.cfun = 0\n            FAKE_MODEM = fakeModem        \n            # This should pass without any problem, and AT+CFUN=1 should be set during connect()\n            cfunWritten = [False]\n            def writeCallbackFunc(data):\n                if data == 'AT+CFUN=1\\r':\n                    cfunWritten[0] = True\n            global SERIAL_WRITE_CALLBACK_FUNC\n            SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc         \n            mockSerial = MockSerialPackage()\n            gsmmodem.serial_comms.serial = mockSerial\n            modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n            modem.connect()\n            SERIAL_WRITE_CALLBACK_FUNC = None\n            self.assertTrue(cfunWritten[0], 'Modem CFUN setting not set to 1 during connect()')\n            modem.close()\n            FAKE_MODEM = None\n    \n    def test_cfunNotSupported(self):\n        \"\"\" Tests case where a modem does not support the AT+CFUN command \"\"\"\n        global FAKE_MODEM            \n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        FAKE_MODEM.cfun = -1 # disable\n        FAKE_MODEM.responses['AT+CFUN?\\r'] = ['ERROR\\r\\n']\n        FAKE_MODEM.responses['AT+CFUN=1\\r'] = ['ERROR\\r\\n']\n        # This should pass without any problem, and AT+CFUN? should at least have been checked during connect()\n        cfunWritten = [False]\n        def writeCallbackFunc(data):\n            if data == 'AT+CFUN?\\r':\n                cfunWritten[0] = True\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n        modem.connect()\n        SERIAL_WRITE_CALLBACK_FUNC = None        \n        self.assertTrue(cfunWritten[0], 'Modem CFUN setting not set to 1 during connect()')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_commandNotSupported(self):\n        \"\"\" Some Huawei modems response with \"COMMAND NOT SUPPORT\" instead of \"ERROR\" or \"OK\"; ensure we detect this \"\"\"\n        global FAKE_MODEM\n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        FAKE_MODEM.responses['AT+WIND?\\r'] = ['COMMAND NOT SUPPORT\\r\\n']\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        modem.connect()\n        self.assertRaises(CommandError, modem.write, 'AT+WIND?')\n        modem.close()\n        FAKE_MODEM = None\n        \n    def test_wavecomConnectSpecifics(self):\n        \"\"\" Wavecom-specific test cases that might not be covered by the modem profiles in fakemodems.py\n        - this is mostly to attain 100% code coverage in tests\n        \"\"\"\n        global FAKE_MODEM\n        FAKE_MODEM = copy(fakemodems.WavecomMultiband900E1800())\n        # Test the case where AT+CLAC returns a response for Wavecom devices, and it includes +WIND and +VTS\n        FAKE_MODEM.responses['AT+CLAC\\r'] = ['+CLAC: D,+CUSD,+WIND,+VTS\\r\\n', 'OK\\r\\n']\n        # Test the case where the +WIND setting is already what we want it to be\n        FAKE_MODEM.responses['AT+WIND?\\r'] = ['+WIND: 50\\r\\n', 'OK\\r\\n']\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        modem.connect()\n        self.assertTrue(gsmmodem.modem.Call.dtmfSupport, '+VTS in AT+CLAC response should have indicated DTMF support')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_zteConnectSpecifics(self):\n        \"\"\" ZTE-specific test cases that might not be covered by the modem profiles in fakemodems.py\n        - this is mostly to attain 100% code coverage in tests\n        \"\"\"\n        global FAKE_MODEM\n        FAKE_MODEM = copy(fakemodems.ZteK3565Z())\n        # Test the case where AT+CLAC returns a response for ZTE devices, and it includes +ZPAS and +VTS\n        FAKE_MODEM.responses['AT+CLAC\\r'][-1] = '+ZPAS\\r\\n'\n        FAKE_MODEM.responses['AT+CLAC\\r'].append('OK\\r\\n')\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        modem.connect()\n        self.assertTrue(gsmmodem.modem.Call.dtmfSupport, '+VTS in AT+CLAC response should have indicated DTMF support')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_huaweiConnectSpecifics(self):\n        \"\"\" Huawei-specific test cases that might not be covered by the modem profiles in fakemodems.py\n        - this is mostly to attain 100% code coverage in tests\n        \"\"\"\n        global FAKE_MODEM\n        FAKE_MODEM = copy(fakemodems.HuaweiK3715())\n        # Test the case where AT+CLAC returns no response for Huawei devices; causing the need for other methods to detect phone type\n        FAKE_MODEM.responses['AT+CLAC\\r'] = ['ERROR\\r\\n']\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        modem.connect()\n        # Huawei modems should have DTMF support\n        self.assertTrue(gsmmodem.modem.Call.dtmfSupport, 'Huawei modems should have DTMF support')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_smscSpecifiedBeforeConnect(self):\n        \"\"\" Tests connect() operation when an SMSC number is set before connect() is called \"\"\"\n        smscNumber = '123454321'\n        global FAKE_MODEM\n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        FAKE_MODEM.smsc = None\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        # Look for the AT+CSCA write\n        cscaWritten = [False]\n        def writeCallbackFunc(data):\n            if data == 'AT+CSCA=\"{0}\"\\r'.format(smscNumber):\n                cscaWritten[0] = True\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        # Set the SMSC number before calling connect()\n        modem.smsc = smscNumber\n        self.assertFalse(cscaWritten[0])\n        modem.connect()\n        self.assertTrue(cscaWritten[0], 'Preset SMSC value not written to modem during connect()')\n        self.assertEqual(modem.smsc, smscNumber, 'Pre-set SMSC not stored correctly during connect()')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_cpmsNotSupported(self):\n        \"\"\" Tests case where a modem does not support the AT+CPMS command \"\"\"\n        global FAKE_MODEM            \n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        FAKE_MODEM.responses['AT+CPMS=?\\r'] = ['+CMS ERROR: 302\\r\\n']\n        # This should pass without any problem, and AT+CPMS=? should at least have been checked during connect()\n        cpmsWritten = [False]\n        def writeCallbackFunc(data):\n            if data == 'AT+CPMS=?\\r':\n                cpmsWritten[0] = True\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n        modem.connect()\n        SERIAL_WRITE_CALLBACK_FUNC = None        \n        self.assertTrue(cpmsWritten[0], 'Modem CPMS allowed values not checked during connect()')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_cnmiNotSupported(self):\n        \"\"\" Tests case where a modem does not support the AT+CNMI command (but does support other SMS-related commands) \"\"\"\n        global FAKE_MODEM            \n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        FAKE_MODEM.responses['AT+CNMI=2,1,0,2\\r'] = ['ERROR\\r\\n']\n        FAKE_MODEM.responses['AT+CNMI=2,1,0,1,0\\r'] = ['ERROR\\r\\n']\n        # This should pass without any problem, and AT+CNMI=2,1,0,2 should at least have been attempted during connect()\n        cnmiWritten = [False]\n        def writeCallbackFunc(data):\n            if data == 'AT+CNMI=2,1,0,2\\r':\n                cnmiWritten[0] = True\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n        modem.connect()\n        SERIAL_WRITE_CALLBACK_FUNC = None        \n        self.assertTrue(cnmiWritten[0], 'AT+CNMI setting not written to modem during connect()')\n        self.assertFalse(modem._smsReadSupported, 'Modem\\'s internal SMS read support flag should be False if AT+CNMI is not supported')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_clipNotSupported(self):\n        \"\"\" Tests case where a modem does not support the AT+CLIP command \"\"\"\n        global FAKE_MODEM            \n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        FAKE_MODEM.responses['AT+CLIP=1\\r'] = ['ERROR\\r\\n']\n        # This should pass without any problem, and AT+CLIP=1 should at least have been attempted during connect()\n        clipWritten = [False]\n        crcWritten = [False]\n        def writeCallbackFunc(data):\n            if data == 'AT+CLIP=1\\r':\n                clipWritten[0] = True\n            elif data == 'AT+CRC=1\\r':\n                crcWritten[0] = True\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n        modem.connect()\n        SERIAL_WRITE_CALLBACK_FUNC = None        \n        self.assertTrue(clipWritten[0], 'AT+CLIP=1 not written to modem during connect()')\n        self.assertFalse(crcWritten[0], 'AT+CRC=1 should not be attempted if AT+CLIP is not supported')\n        self.assertFalse(modem._callingLineIdentification, 'Modem\\'s internal calling line identification flag should be False if AT+CLIP is not supported')\n        self.assertFalse(modem._extendedIncomingCallIndication, 'Modem\\'s internal extended calling line identification information flag should be False if AT+CLIP is not supported')\n        modem.close()\n        FAKE_MODEM = None\n\n    def test_crcNotSupported(self):\n        \"\"\" Tests case where a modem does not support the AT+CRC command \"\"\"\n        global FAKE_MODEM            \n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        FAKE_MODEM.responses['AT+CRC=1\\r'] = ['ERROR\\r\\n']\n        # This should pass without any problem, and AT+CRC=1 should at least have been attempted during connect()\n        clipWritten = [False]\n        crcWritten = [False]\n        def writeCallbackFunc(data):\n            if data == 'AT+CLIP=1\\r':\n                clipWritten[0] = True\n            elif data == 'AT+CRC=1\\r':\n                crcWritten[0] = True\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n        modem.connect()\n        SERIAL_WRITE_CALLBACK_FUNC = None        \n        self.assertTrue(clipWritten[0], 'AT+CLIP=1 not written to modem during connect()')\n        self.assertTrue(crcWritten[0], 'AT+CRC=1 not written to modem during connect()')\n        self.assertTrue(modem._callingLineIdentification, 'Modem\\'s internal calling line identification flag should be True if AT+CLIP is supported')\n        self.assertFalse(modem._extendedIncomingCallIndication, 'Modem\\'s internal extended calling line identification information flag should be False if AT+CRC is not supported')\n        modem.close()\n        FAKE_MODEM = None\n\n\nclass TestGsmModemDial(unittest.TestCase):\n\n    def tearDown(self):\n        self.modem.close()\n        global FAKE_MODEM\n        FAKE_MODEM = None\n    \n    def init_modem(self, modem):\n        global FAKE_MODEM\n        FAKE_MODEM = modem\n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial        \n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        self.modem.connect()\n    \n    def test_dial(self):\n        \"\"\" Tests dialing without specifying a callback function \"\"\"\n        \n        tests = (['0123456789', '1', '0'],)\n        \n        global MODEMS\n        testModems = fakemodems.createModems()\n        testModems.append(fakemodems.GenericTestModem()) # Test polling only\n        for fakeModem in testModems:\n            self.init_modem(fakeModem)\n            \n            modem = self.modem.serial.modem # load the copy()-ed modem instance\n            \n            for number, callId, callType in tests:\n                def writeCallbackFunc(data):\n                    if self.modem._mustPollCallStatus and data.startswith('AT+CLCC'):\n                        return # Can happen due to polling\n                    self.assertEqual('ATD{0};\\r'.format(number), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\". Modem: {2}'.format('ATD{0};'.format(number), data[:-1] if data[-1] == '\\r' else data, modem))\n                    self.modem.serial.writeCallbackFunc = None\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc                \n                self.modem.serial.responseSequence = modem.getAtdResponse(number)\n                self.modem.serial.responseSequence.extend(modem.getPreCallInitWaitSequence())\n                # Fake call initiated notification\n                self.modem.serial.responseSequence.extend(modem.getCallInitNotification(callId, callType))\n                call = self.modem.dial(number)\n                # Wait for the read buffer to clear\n                while len(self.modem.serial._readQueue) > 0 or len(self.modem.serial.responseSequence) > 0:\n                    time.sleep(0.05)\n                if self.modem._mustPollCallStatus:\n                    time.sleep(0.6)\n                self.assertIsInstance(call, gsmmodem.modem.Call)\n                self.assertIs(call.number, number)\n                # Check status\n                self.assertTrue(call.active, 'Call state invalid: should be active. Modem: {0}'.format(modem))\n                self.assertFalse(call.answered, 'Call state invalid: should not yet be answered. Modem: {0}'.format(modem))            \n                self.assertIn(call.id, self.modem.activeCalls)\n                self.assertEqual(len(self.modem.activeCalls), 1)\n                # Fake an answer\n                self.modem.serial.responseSequence = modem.getRemoteAnsweredNotification(callId, callType)\n                # Wait a bit for the event to be picked up\n                while len(self.modem.serial._readQueue) > 0 or len(self.modem.serial.responseSequence) > 0:                    \n                    time.sleep(0.05)\n                if self.modem._mustPollCallStatus:\n                    time.sleep(0.6) # Ensure polling picks up event\n                elif not self.modem._waitForCallInitUpdate:\n                    time.sleep(0.1) # Ensure event is picked up\n                self.assertTrue(call.answered, 'Remote call answer was not detected. Modem: {0}'.format(modem))\n                self.assertTrue(call.active, 'Call state invalid: should be active. Modem: {0}'.format(modem))\n                def hangupCallback(data):\n                    if self.modem._mustPollCallStatus and data.startswith('AT+CLCC'):\n                        return # Can happen due to polling\n                    self.assertEqual('ATH\\r'.format(number), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\". Modem: {2}'.format('ATH'.format(number), data[:-1] if data[-1] == '\\r' else data, modem))\n                self.modem.serial.writeCallbackFunc = hangupCallback\n                call.hangup()\n                self.assertFalse(call.answered, 'Hangup call did not change answered state. Modem: {0}'.format(modem))\n                self.assertFalse(call.active, 'Call state invalid: should not be active (local hangup). Modem: {0}'.format(modem))\n                self.assertNotIn(call.id, self.modem.activeCalls)\n                self.assertEqual(len(self.modem.activeCalls), 0)\n\n                ############## Check remote hangup detection ###############\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc\n                self.modem.serial.responseSequence = modem.getAtdResponse(number)\n                self.modem.serial.responseSequence.extend(modem.getPreCallInitWaitSequence())\n                # Fake call initiated notification\n                self.modem.serial.responseSequence.extend(modem.getCallInitNotification(callId, callType))                \n                call = self.modem.dial(number)\n                self.assertTrue(call.active, 'Call state invalid: should be active. Modem: {0}'.format(modem))\n                # Wait a bit for the event to be picked up\n                while len(self.modem.serial._readQueue) > 0 or len(self.modem.serial.responseSequence) > 0:\n                    time.sleep(0.05)\n                if self.modem._mustPollCallStatus:\n                    time.sleep(0.6) # Ensure polling picks up event\n                # Fake remote answer\n                self.modem.serial.responseSequence = modem.getRemoteAnsweredNotification(callId, callType)\n                while len(self.modem.serial._readQueue) > 0 or len(self.modem.serial.responseSequence) > 0:\n                    time.sleep(0.05)\n                if self.modem._mustPollCallStatus:\n                    time.sleep(0.5) # Ensure polling picks up event\n                elif not self.modem._waitForCallInitUpdate:\n                    time.sleep(0.1) # Ensure event is picked up\n                self.assertTrue(call.answered, 'Remote call answer was not detected. Modem: {0}'.format(modem))\n                self.assertIn(call.id, self.modem.activeCalls)\n                self.assertEqual(len(self.modem.activeCalls), 1)\n                # Now fake a remote hangup\n                self.modem.serial.responseSequence = modem.getRemoteHangupNotification(callId, callType)\n                # Wait a bit for the event to be picked up\n                while len(self.modem.serial._readQueue) > 0 or len(self.modem.serial.responseSequence) > 0:\n                    time.sleep(0.05)\n                if self.modem._mustPollCallStatus:\n                    time.sleep(0.6) # Ensure polling picks up event\n                self.assertFalse(call.answered, 'Remote hangup was not detected. Modem: {0}'.format(modem))\n                self.assertFalse(call.active, 'Call state invalid: should not be active (remote hangup). Modem: {0}'.format(modem))\n                self.assertNotIn(call.id, self.modem.activeCalls)\n                self.assertEqual(len(self.modem.activeCalls), 0)\n\n                ############## Check remote call rejection (hangup before answering) ###############\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc\n                self.modem.serial.responseSequence = modem.getAtdResponse(number)\n                self.modem.serial.responseSequence.extend(modem.getPreCallInitWaitSequence())\n                # Fake call initiated notification\n                self.modem.serial.responseSequence.extend(modem.getCallInitNotification(callId, callType))\n                call = self.modem.dial(number)\n                self.assertTrue(call.active, 'Call state invalid: should be active. Modem: {0}'.format(modem))\n                # Wait a bit for the event to be picked up\n                while len(self.modem.serial._readQueue) > 0 or len(self.modem.serial.responseSequence) > 0:\n                    time.sleep(0.05)\n                if self.modem._mustPollCallStatus:\n                    time.sleep(0.6) # Ensure polling picks up event\n                self.assertFalse(call.answered, 'Call should not have been in \"answered\" state. Modem: {0}'.format(modem))\n                self.assertIn(call.id, self.modem.activeCalls)\n                self.assertEqual(len(self.modem.activeCalls), 1)\n                # Now reject the call\n                self.modem.serial.responseSequence = modem.getRemoteRejectCallNotification(callId, callType)\n                # Wait a bit for the event to be picked up\n                while len(self.modem.serial._readQueue) > 0 or len(self.modem.serial.responseSequence) > 0:\n                    time.sleep(0.05)\n                if self.modem._mustPollCallStatus:\n                    time.sleep(0.6) # Ensure polling picks up event\n                time.sleep(0.05)\n                self.assertFalse(call.answered, 'Call state invalid: should not be answered (remote call rejection). Modem: {0}'.format(modem))\n                self.assertFalse(call.active, 'Call state invalid: should not be active (remote rejection). Modem: {0}'.format(modem))\n                self.assertNotIn(call.id, self.modem.activeCalls)\n                self.assertEqual(len(self.modem.activeCalls), 0)\n            self.modem.close()\n\n        def test_dialCallback(self):\n            \"\"\" Tests the dial method's callback mechanism \"\"\"\n            tests = (['12345678', '1', '0'],)\n\n            global MODEMS\n            testModems = fakemodems.createModems()\n            testModems.append(fakemodems.GenericTestModem()) # Test polling only\n            for fakeModem in testModems:\n                self.init_modem(fakeModem)\n\n                modem = self.modem.serial.modem # load the copy()-ed modem instance\n\n                for number, callId, callType in tests:\n\n                    callbackVars = [None, False, 0]\n\n                    def callUpdateCallbackFunc1(call):\n                        self.assertIsInstance(call, gsmmodem.modem.Call)\n                        self.assertEqual(call, callbackVars[0])\n                        # Check call status\n                        if callbackVars[2] == 0: # Expected \"answer\" event\n                            self.assertTrue(call.active, 'Call state invalid: should be active. Modem: {0}'.format(modem))\n                            self.assertTrue(call.answered, 'Call state invalid: should have been answered. Modem: {0}'.format(modem))\n                        elif callbackVars[2] == 1: # Expected \"hangup\" event\n                            self.assertFalse(call.answered, 'Call state invalid: \"answered\" should be false after hangup. Modem: {0}'.format(modem))\n                            self.assertFalse(call.active, 'Call state invalid: should be inactive. Modem: {0}'.format(modem))\n                        callbackVars[1] = True # set \"callback called\" flag\n\n                    call = self.modem.dial(number, callStatusUpdateCallbackFunc=callUpdateCallbackFunc1)\n                    self.assertIsInstance(call, gsmmodem.modem.Call)\n                    callbackVars[0] = call\n                    self.assertTrue(call.active, 'Call state invalid: should be active. Modem: {0}'.format(modem))\n                    self.assertFalse(call.answered, 'Call state invalid: should not yet be answered. Modem: {0}'.format(modem))\n                    # Fake an answer...\n                    self.modem.serial.responseSequence = modem.getRemoteAnsweredNotification(callId, callType)\n                    # ...and wait for the callback to be called\n                    while not callbackVars[1]:\n                        time.sleep(0.05)\n                    # Double check local call variable\n                    self.assertTrue(call.active, 'Call state invalid: should be active. Modem: {0}'.format(modem))\n                    self.assertTrue(call.answered, 'Call state invalid: should have been answered. Modem: {0}'.format(modem))\n                    # Fake remote hangup...\n                    callbackVars[1] = False\n                    callbackVars[2] = 1\n                    self.modem.serial.responseSequence = modem.getRemoteAnsweredNotification(callId, callType)\n                    # ...and wait for the callback to be called\n                    while not callbackVars[1]:\n                        time.sleep(0.05)\n                    # Double check local call variable\n                    self.assertFalse(call.answered, 'Call state invalid: \"answered\" should be false after hangup. Modem: {0}'.format(modem))\n                    self.assertFalse(call.active, 'Call state invalid: should be inactive. Modem: {0}'.format(modem))\n\n            self.modem.close()\n    \n    def test_dialError(self):\n        \"\"\" Test error handling when dialing \"\"\"\n        self.init_modem(fakemodems.HuaweiK3715()) # Use a modem that supports call update notifications\n        self.modem.serial.responseSequence = ['+CME ERROR: 30\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.CmeError, self.modem.dial, '123')\n        self.modem.serial.responseSequence = ['+CMS ERROR: 500\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.CmsError, self.modem.dial, '123')\n        self.modem.serial.responseSequence = ['ERROR\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.CommandError, self.modem.dial, '123')\n    \n    def test_dial_callInitEventTimeout(self):\n        \"\"\" Test dial() timeout event: call initiated event never occurs \"\"\"\n        self.init_modem(fakemodems.HuaweiK3715()) # Use a modem that supports call update notifications\n        # The following should timeout very quickly - ATD does not timeout, but no call is established\n        self.assertRaises(gsmmodem.exceptions.TimeoutException, self.modem.dial, **{'number': '123', 'timeout': 0.05})\n    \n    def test_dial_atdTimeout(self):\n        \"\"\" Test dial() timeout event: ATD command timeout \"\"\"\n        self.init_modem(fakemodems.GenericTestModem())\n        # Disable ATD response\n        self.modem.serial.modem.responses['ATD123;\\r'] = []\n        # The following should timeout very quickly - no ATD command response received\n        self.assertRaises(gsmmodem.exceptions.TimeoutException, self.modem.dial, **{'number': '123', 'timeout': 0.05})\n\n\nclass TestGsmModemPinConnect(unittest.TestCase):\n    \"\"\" Tests PIN unlocking and connect() method of GsmModem class (excluding connect/close) \"\"\"\n    \n    def tearDown(self):\n        global FAKE_MODEM\n        FAKE_MODEM = None\n    \n    def init_modem(self, modem):\n        global FAKE_MODEM\n        FAKE_MODEM = modem\n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')        \n        \n    def test_connectPinLockedNoPin(self):\n        \"\"\" Test connecting to the modem with a SIM PIN code - no PIN specified\"\"\"\n        testModems = fakemodems.createModems()\n        for modem in testModems:\n            modem.pinLock = True\n            self.init_modem(modem)\n            self.assertRaises(PinRequiredError, self.modem.connect)\n            self.modem.close()\n    \n    def test_connectPinLockedWithPin(self):\n        \"\"\" Test connecting to the modem with a SIM PIN code - PIN specified\"\"\"\n        testModems = fakemodems.createModems()\n        # Also test a modem that allows only CMEE commands before PIN is entered\n        edgeCaseModem = fakemodems.GenericTestModem()\n        edgeCaseModem.commandsNoPinRequired = ['AT+CMEE=1\\r']\n        testModems.append(edgeCaseModem)\n        for modem in testModems:\n            modem.pinLock = True\n            self.init_modem(modem)\n            # This should succeed\n            try:\n                self.modem.connect(pin='1234')\n            except PinRequiredError:\n                self.fail(\"Pin required exception thrown for modem {0}\".format(modem))\n            finally:\n                self.modem.close()\n    \n    def test_connectPin_incorrect(self):\n        \"\"\" Test connecting to the modem with a SIM PIN code - incorrect PIN specified \"\"\"\n        def writeCallbackFunc(data):\n            if data.startswith('AT+CPIN=\"'):\n                # Fake \"incorrect PIN\" response\n                self.modem.serial.responseSequence = ['+CME ERROR: 16\\r\\n']\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        fakeModem = fakemodems.GenericTestModem()\n        fakeModem.pinLock = True\n        self.init_modem(fakeModem)\n        self.assertRaises(gsmmodem.exceptions.IncorrectPinError, self.modem.connect, **{'pin': '1234'})\n        self.modem.close()\n        SERIAL_WRITE_CALLBACK_FUNC = None\n    \n    def test_connectPin_pukRequired(self):\n        \"\"\" Test connecting to the modem with a SIM PIN code - SIM locked; PUK required \"\"\"\n        def writeCallbackFunc(data):\n            if data.startswith('AT+CPIN=\"'):\n                # Fake \"PUK required\" response\n                self.modem.serial.responseSequence = ['+CME ERROR: 12\\r\\n']\n        global SERIAL_WRITE_CALLBACK_FUNC\n        SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n        fakeModem = fakemodems.GenericTestModem()\n        fakeModem.pinLock = True\n        self.init_modem(fakeModem)\n        self.assertRaises(gsmmodem.exceptions.PukRequiredError, self.modem.connect, **{'pin': '1234'})\n        self.modem.close()\n        SERIAL_WRITE_CALLBACK_FUNC = None\n    \n    def test_connectPin_timeoutEvents(self):\n        \"\"\" Test different TimeoutException scenarios when checking PIN status (github issue #19) \"\"\"\n        \n        tests = (([0.05], True), (['+CPIN: READY\\r\\n'], False), (['FIRST LINE\\r\\n', 'SECOND LINE\\r\\n'], True))\n        \n        for response, shouldTimeout in tests:\n            def writeCallbackFunc(data):\n                if data.startswith('AT+CPIN?'):\n                    # Fake \"incorrect PIN\" response\n                    self.modem.serial.responseSequence = response\n        \n            global SERIAL_WRITE_CALLBACK_FUNC\n            SERIAL_WRITE_CALLBACK_FUNC = writeCallbackFunc\n            fakeModem = fakemodems.GenericTestModem()\n            fakeModem.pinLock = False\n            self.init_modem(fakeModem)\n            if shouldTimeout:\n                self.assertRaises(gsmmodem.exceptions.TimeoutException, self.modem.connect)\n            else:\n                self.modem.connect() # should run fine\n            self.modem.close()\n            SERIAL_WRITE_CALLBACK_FUNC = None\n\n\nclass TestIncomingCall(unittest.TestCase):\n    \n    def tearDown(self):\n        global FAKE_MODEM\n        FAKE_MODEM = None\n        self.modem.close()\n    \n    def init_modem(self, modem, incomingCallCallbackFunc):\n        global FAKE_MODEM\n        FAKE_MODEM = modem\n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial        \n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --', incomingCallCallbackFunc=incomingCallCallbackFunc)\n        self.modem.connect()\n    \n    def test_incomingCallAnswer(self):\n\n        for modem in fakemodems.createModems():\n            callReceived = [False, 'VOICE', '']\n            def incomingCallCallbackFunc(call):\n                try:                    \n                    self.assertIsInstance(call, gsmmodem.modem.IncomingCall)\n                    self.assertIn(call.id, self.modem.activeCalls)\n                    self.assertEqual(len(self.modem.activeCalls), 1)\n                    self.assertEqual(call.number, callReceived[2], 'Caller ID (caller number) incorrect. Expected: \"{0}\", got: \"{1}\". Modem: {2}'.format(callReceived[2], call.number, modem))\n                    self.assertFalse(call.answered, 'Call state invalid: should not yet be answered. Modem: {0}'.format(modem))\n                    self.assertIsInstance(call.type, int)\n                    self.assertEqual(call.type, callReceived[1], 'Invalid call type; expected \"{0}\", got \"{1}\". Modem: {2}'.format(callReceived[1], call.type, modem))\n                    def writeCallbackFunc1(data):\n                        self.assertEqual('ATA\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\". Modem: {2}'.format('ATA\\r', data, modem))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc1\n                    call.answer()\n                    self.assertTrue(call.answered, 'Call state invalid: should be answered. Modem: {0}'.format(modem))\n                    # Call answer() again - shouldn't do anything\n                    def writeCallbackShouldNotBeCalled(data):\n                        self.fail('Nothing should have been written to modem, but got: {0}'.format(data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackShouldNotBeCalled\n                    call.answer()\n                    # Hang up\n                    def writeCallbackFunc2(data):\n                        self.assertEqual('ATH\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\". Modem: {2}'.format('ATH\\r', data, modem))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                    call.hangup()\n                    self.assertFalse(call.answered, 'Call state invalid: hangup did not change call state. Modem: {0}'.format(modem))\n                    self.assertNotIn(call.id, self.modem.activeCalls)\n                    self.assertEqual(len(self.modem.activeCalls), 0)\n                    # Call hangup() again - shouldn't do anything\n                    self.modem.serial.writeCallbackFunc = writeCallbackShouldNotBeCalled\n                    call.hangup()\n                finally:\n                    callReceived[0] = True\n        \n            self.init_modem(modem, incomingCallCallbackFunc)\n        \n            tests = (('+27820001234', 'VOICE', 0),)\n        \n            for number, cringParam, callType in tests:\n                callReceived[0] = False\n                callReceived[1] = callType\n                callReceived[2] = number\n                # Fake incoming voice call                \n                self.modem.serial.responseSequence = modem.getIncomingCallNotification(number, cringParam)\n                # Wait for the handler function to finish\n                while callReceived[0] == False:\n                    time.sleep(0.05)\n            self.modem.close()\n    \n    def test_incomingCallCrcNotSupported(self):\n        \"\"\" Tests handling incoming calls without +CRC support \"\"\"\n        callReceived = [False]\n        def callbackFunc(call):\n            self.assertIsInstance(call, gsmmodem.modem.IncomingCall)\n            self.assertEqual(call.type, None, 'Invalid call type; expected \"{0}\", got \"{1}\".'.format(None, call.type))\n            callReceived[0] = True\n        \n        testModem = copy(fakemodems.GenericTestModem())\n        testModem.responses['AT+CRC?\\r'] = ['ERROR\\r\\n']\n        testModem.responses['AT+CRC=1\\r'] = ['ERROR\\r\\n']\n        self.init_modem(testModem, incomingCallCallbackFunc=callbackFunc)\n        \n        # Ensure extended incoming call indications are active\n        self.assertFalse(self.modem._extendedIncomingCallIndication, 'Extended incoming call indicator flag should be False')\n        # Fake incoming voice call using basic incoming call indication format\n        self.modem.serial.responseSequence = ['RING\\r\\n', '+CLIP: \"+27821231234\",145,,,,0\\r\\n']\n        # Wait for the handler function to finish\n        while callReceived[0] == False:\n            time.sleep(0.1)\n        self.assertFalse(self.modem._extendedIncomingCallIndication, 'Extended incoming call indicator flag should be False')\n    \n    def test_incomingCallCrcChangedExternally(self):\n        \"\"\" Tests handling incoming call notifications when the +CRC setting \\\n        was modfied by some external program (issue #18) \"\"\"\n        \n        callReceived = [False]\n        def callbackFunc(call):\n            self.assertIsInstance(call, gsmmodem.modem.IncomingCall)\n            callReceived[0] = True\n        \n        self.init_modem(None, incomingCallCallbackFunc=callbackFunc)\n        \n        # Ensure extended incoming call indications are active\n        self.assertTrue(self.modem._extendedIncomingCallIndication, 'Extended incoming call indicator flag should be True')\n        # Fake incoming voice call using extended incoming call indication format\n        self.modem.serial.responseSequence = ['+CRING: VOICE\\r\\n', '+CLIP: \"+27821231234\",145,,,,0\\r\\n']\n        # Wait for the handler function to finish\n        while callReceived[0] == False:\n            time.sleep(0.1)\n        callReceived[0] = False\n        # Now fake incoming call using basic incoming call indication format (without informing GsmModem class about change)\n        self.modem.serial.responseSequence = ['RING\\r\\n', '+CLIP: \"+27821231234\",145,,,,0\\r\\n']\n        # Wait for the handler function to finish\n        while callReceived[0] == False:\n            time.sleep(0.05)\n        # Ensure extended incoming call indications have been re-enabled\n        self.assertTrue(self.modem._extendedIncomingCallIndication, 'Extended incoming call indicator flag should be True')\n        \n        # Now repeat the test, but cause re-enabling the +CRC setting to fail\n        self.modem.serial.modem.responses['AT+CRC=1\\r'] = ['ERROR\\r\\n']\n        callReceived[0] = False\n        # Basic incoming call indication format (without informing GsmModem class about change)\n        self.modem.serial.responseSequence = ['RING\\r\\n', '+CLIP: \"+27821231234\",145,,,,0\\r\\n']\n        # Wait for the handler function to finish\n        while callReceived[0] == False:\n            time.sleep(0.05)\n        # Since re-enabling the extended format failed,  extended incoming call indications flag should be False\n        self.assertFalse(self.modem._extendedIncomingCallIndication, 'Extended incoming call indicator flag should be False because AT+CRC=1 failed')\n\n\nclass TestCall(unittest.TestCase):\n    \"\"\" Tests Call object APIs that are not covered by TestIncomingCall and TestGsmModemDial \"\"\"\n    \n    def init_modem(self, modem):\n        global FAKE_MODEM\n        FAKE_MODEM = modem\n        gsmmodem.serial_comms.serial = MockSerialPackage()\n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --')\n        self.modem.connect()\n        FAKE_MODEM = None\n\n    def testDtmf(self):\n        \"\"\" Tests sending DTMF tones in a phone call \"\"\"\n        originalBaseDtmfCommand = gsmmodem.modem.Call.DTMF_COMMAND_BASE\n        for fakeModem in fakemodems.createModems():\n            gsmmodem.modem.Call.DTMF_COMMAND_BASE = originalBaseDtmfCommand\n            self.init_modem(fakeModem)\n            # Make sure everything is set up correctly during connect()\n            self.assertEqual(gsmmodem.modem.Call.DTMF_COMMAND_BASE, fakeModem.dtmfCommandBase, 'Invalid base DTMF command for modem: {0}; expected \"{1}\", got \"{2}\"'.format(fakeModem, fakeModem.dtmfCommandBase, gsmmodem.modem.Call.DTMF_COMMAND_BASE))\n            # Test sending DTMF tones in a call\n            call = gsmmodem.modem.Call(self.modem, 1, 1, '+270000000')\n            call.answered = True\n            \n            tests = (('3', 'AT{0}3\\r'.format(fakeModem.dtmfCommandBase.format(cid=call.id))),\n                     ('1234', 'AT{0}1;{0}2;{0}3;{0}4\\r'.format(fakeModem.dtmfCommandBase.format(cid=call.id))),\n                     ('#0*', 'AT{0}#;{0}0;{0}*\\r'.format(fakeModem.dtmfCommandBase.format(cid=call.id))))\n\n            for tones, expectedCommand in tests:\n                def writeCallbackFunc(data):\n                    expectedCommand = 'AT{0}{1}\\r'.format(fakeModem.dtmfCommandBase.format(cid=call.id), tones[self.currentTone])\n                    self.currentTone += 1;\n                    self.assertEqual(expectedCommand, data, 'Invalid data written to modem for tones: \"{0}\"; expected \"{1}\", got: \"{2}\". Modem: {3}'.format(tones, expectedCommand[:-1].format(cid=self.id), data[:-1] if data[-1] == '\\r' else data, fakeModem))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc\n                self.currentTone = 0;\n            \n            # Now attempt to send DTMF tones in an inactive call\n            self.modem.serial.writeCallbackFunc = None\n            call.hangup()\n            self.assertRaises(gsmmodem.exceptions.InvalidStateException, call.sendDtmfTone, '1')\n            self.modem.close()\n        gsmmodem.modem.Call.DTMF_COMMAND_BASE = originalBaseDtmfCommand\n    \n    def testDtmfInterrupted(self):\n        \"\"\" Tests interrupting the playback of DTMF tones \"\"\"\n        self.init_modem(fakemodems.GenericTestModem())\n        call = gsmmodem.modem.Call(self.modem, 1, 1, '+270000000')\n        call.answered = True\n        # Fake an interruption - no network service\n        self.modem.serial.responseSequence = [0.1, '+CME ERROR: 30\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.InterruptedException, call.sendDtmfTone, '5')\n        # Fake an interruption - operation not allowed\n        self.modem.serial.responseSequence = [0.1, '+CME ERROR: 3\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.InterruptedException, call.sendDtmfTone, '5')\n        # Fake some other CME error\n        self.modem.serial.responseSequence = [0.1, '+CME ERROR: 1234\\r\\n']\n        self.assertRaises(gsmmodem.exceptions.CmeError, call.sendDtmfTone, '5')\n        self.modem.close()\n        \n    def testCallAnsweredCallback(self):\n        \"\"\" Tests Call object's \"call answered\" callback mechanism \"\"\"\n        self.init_modem(fakemodems.GenericTestModem())\n        \n        callbackCalled = [False]\n        def callbackFunc(callObj):\n            self.assertEqual(callObj, call)\n            callbackCalled[0] = True\n        call = gsmmodem.modem.Call(self.modem, 1, 1, '+270000000', callStatusUpdateCallbackFunc=callbackFunc)\n        # Answer the call \"remotely\" - this should trigger the callback\n        call.answered = True\n        self.assertTrue(callbackCalled[0], \"Call status update callback not called for answer event\")\n        self.modem.close()\n\n\nclass TestSms(unittest.TestCase):\n    \"\"\" Tests the SMS API of GsmModem class \"\"\"\n    \n    def setUp(self):\n        self.tests = (('+0123456789', 'Hello world!',                        \n                       1,\n                       datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                       '+2782913593',\n                       '06917228195339040A9110325476980000313080512061800CC8329BFD06DDDF72363904', 29, 142,\n                       'SM'),\n                      ('+9876543210', \n                       'Hallo\\nhoe gaan dit?', \n                       4,\n                       datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                       '+2782913593',\n                       '06917228195339040A91896745230100003130805120618013C8309BFD56A0DF65D0391C7683C869FA0F', 35, 33, \n                       'SM'),\n                      ('+353870000000', 'My message',\n                       13,\n                       datetime(2013, 4, 20, 20, 22, 27, tzinfo=SimpleOffsetTzInfo(4)),\n                       None, None, 0, 0, 'ME'),\n                      )\n        # address_text data to use for tests when testing PDU mode\n        self.testsPduAddressText = ('', '\"abc123\"', '\"\"', 'Test User 123', '9876543231')\n    \n    def initModem(self, smsReceivedCallbackFunc):\n        # Override the pyserial import        \n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --', smsReceivedCallbackFunc=smsReceivedCallbackFunc)        \n        self.modem.connect()\n\n    def test_sendSmsLeaveTextModeOnInvalidCharacter(self):\n        \"\"\" Tests sending SMS messages in text mode \"\"\"\n        self.initModem(None)\n        self.modem.smsTextMode = True # Set modem to text mode\n        self.assertTrue(self.modem.smsTextMode)\n        # PDUs checked on https://www.diafaan.com/sms-tutorials/gsm-modem-tutorial/online-sms-pdu-decoder/\n        tests = (('+0123456789', 'Helló worłd!',\n                  1,\n                  datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                  '+2782913593',\n                  [('00218D0A91103254769800081800480065006C006C00F300200077006F0072014200640021', 36, 141)],\n                  'SM',\n                  'UCS2'),\n                 ('+0123456789', 'Hellò wor£d!',\n                  2,\n                  datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                  '82913593',\n                  [('00218E0A91103254769800000CC8329B8D00DDDFF2003904', 23, 142)],\n                  'SM',\n                  'GSM'),\n                 ('+0123456789', '12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 12345-070 12345-080 12345-090 12345-100 12345-110 12345-120 12345-130 12345-140 12345-150 12345-160-Hellò wor£d!',\n                  3,\n                  datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                  '82913593',\n                  [('00618F0A9110325476980000A00500038F020162B219ADD682C560A0986C46ABB560321828269BD16A2DD80C068AC966B45A0B46838162B219ADD682D560A0986C46ABB560361828269BD16A2DD80D068AC966B45A0B86838162B219ADD682E560A0986C46ABB562301828269BD16AAD580C068AC966B45A2B26838162B219ADD68ACD60A0986C46ABB562341828269BD16AAD580D068AC966', 152, 143),\n('00618F0A91103254769800001A0500038F020268B556CC066B21CB6C3602747FCB03E410', 35, 143)],\n                  'SM',\n                  'GSM'),\n                 ('+0123456789', 'Hello world!\\n Hello world!\\n Hello world!\\n Hello world!\\n-> Helló worłd! ',\n                  4,\n                  datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                  '+2782913593',\n                  [('0061900A91103254769800088C05000390020100480065006C006C006F00200077006F0072006C00640021000A002000480065006C006C006F00200077006F0072006C00640021000A002000480065006C006C006F00200077006F0072006C00640021000A002000480065006C006C006F00200077006F0072006C00640021000A002D003E002000480065006C006C00F300200077006F0072', 152, 144),\n                   ('0061900A91103254769800080E0500039002020142006400210020', 26, 144)],\n                  'SM',\n                  'UCS2'),\n('+0123456789', '12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 12345-070 12345-080 12345-090 12345-100 12345-110 12345-120 12345-130 12345-140 12345-150 12345-160-Hello word!',\n                  5,\n                  datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                  '82913593',\n                  [('0061910A9110325476980000A005000391020162B219ADD682C560A0986C46ABB560321828269BD16A2DD80C068AC966B45A0B46838162B219ADD682D560A0986C46ABB560361828269BD16A2DD80D068AC966B45A0B86838162B219ADD682E560A0986C46ABB562301828269BD16AAD580C068AC966B45A2B26838162B219ADD68ACD60A0986C46ABB562341828269BD16AAD580D068AC966', 152, 145),\n('0061910A91103254769800001905000391020268B556CC066B21CB6CF61B747FCBC921', 34, 145)],\n                  'SM',\n                  'GSM'),)\n\n        for number, message, index, smsTime, smsc, pdus, mem, encoding in tests:\n            def writeCallbackFunc(data):\n                def writeCallbackFunc2(data):\n                    # Second step - get available encoding schemes\n                    self.assertEqual('AT+CSCS=?\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS=?', data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n\n                def writeCallbackFunc3(data):\n                    # Third step - set encoding\n                    self.assertEqual('AT+CSCS=\"{0}\"\\r'.format(encoding), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS=\"{0}\"\\r'.format(encoding), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc4\n\n                def writeCallbackFunc4(data):\n                    # Fourth step - send PDU length\n                    tpdu_length = pdus[self.currentPdu][1]\n                    ref = pdus[self.currentPdu][2]\n                    self.assertEqual('AT+CMGS={0}\\r'.format(tpdu_length), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGS={0}'.format(tpdu_length), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc5\n                    self.modem.serial.flushResponseSequence = False\n                    self.modem.serial.responseSequence = ['> \\r\\n', '+CMGS: {0}\\r\\n'.format(ref), 'OK\\r\\n']\n\n                def writeCallbackFuncRaiseError(data):\n                    self.assertEqual(self.currentPdu, len(pdus) - 1, 'Invalid data written to modem; expected {0} PDUs, got {1} PDU'.format(len(pdus), self.currentPdu + 1))\n\n                def writeCallbackFunc5(data):\n                    # Fifth step - send SMS PDU\n                    pdu = pdus[self.currentPdu][0]\n                    self.assertEqual('{0}{1}'.format(pdu, chr(26)), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('{0}{1}'.format(pdu, chr(26)), data))\n                    self.modem.serial.flushResponseSequence = True\n                    self.currentPdu += 1\n                    if len(pdus) > self.currentPdu:\n                        self.modem.serial.writeCallbackFunc = writeCallbackFunc4\n                    else:\n                        self.modem.serial.writeCallbackFunc = writeCallbackFuncRaiseError\n\n                # First step - change to PDU mode\n                self.assertEqual('AT+CMGF=0\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGF=0', data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                self.currentPdu = 0\n                self.modem._smsRef = pdus[self.currentPdu][2]\n\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.modem.serial.flushResponseSequence = True\n            sms = self.modem.sendSms(number, message)\n            self.assertFalse(self.modem.smsTextMode)\n            self.assertEqual(self.modem._smsEncoding, encoding, 'Modem uses invalid encoding. Expected \"{0}\", got \"{1}\"'.format(encoding, self.modem._smsEncoding))\n            self.assertIsInstance(sms, gsmmodem.modem.SentSms)\n            self.assertEqual(sms.number, number, 'Sent SMS has invalid number. Expected \"{0}\", got \"{1}\"'.format(number, sms.number))\n            self.assertEqual(sms.text, message, 'Sent SMS has invalid text. Expected \"{0}\", got \"{1}\"'.format(message, sms.text))\n            self.assertIsInstance(sms.reference, int, 'Sent SMS reference type incorrect. Expected \"{0}\", got \"{1}\"'.format(int, type(sms.reference)))\n            ref = pdus[0][2] # All refference numbers should be equal\n            self.assertEqual(sms.reference, ref, 'Sent SMS reference incorrect. Expected \"{0}\", got \"{1}\"'.format(ref, sms.reference))\n            self.assertEqual(sms.status, gsmmodem.modem.SentSms.ENROUTE, 'Sent SMS status should have been {0} (\"ENROUTE\"), but is: {1}'.format(gsmmodem.modem.SentSms.ENROUTE, sms.status))\n            # Reset mode and encoding\n            self.modem._smsTextMode = True # Set modem to text mode\n            self.modem._smsEncoding = \"GSM\" # Set encoding to GSM-7\n            self.modem._smsSupportedEncodingNames = None # Force modem to ask about possible encoding names\n        self.modem.close()\n\n    def test_sendSmsTextMode(self):\n        \"\"\" Tests sending SMS messages in text mode \"\"\"\n        self.initModem(None)\n        self.modem.smsTextMode = True # Set modem to text mode\n        self.assertTrue(self.modem.smsTextMode)\n        for number, message, index, smsTime, smsc, pdu, tpdu_length, ref, mem in self.tests:\n            self.modem._smsRef = ref\n            def writeCallbackFunc(data):\n                def writeCallbackFunc2(data):\n                    self.assertEqual('{0}{1}'.format(message, chr(26)), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('{0}{1}'.format(message, chr(26)), data))\n                    self.modem.serial.flushResponseSequence = True                \n                self.assertEqual('AT+CMGS=\"{0}\"\\r'.format(number), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGS=\"{0}\"'.format(number), data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.modem.serial.flushResponseSequence = False\n            self.modem.serial.responseSequence = ['> \\r\\n', '+CMGS: {0}\\r\\n'.format(ref), 'OK\\r\\n']\n            sms = self.modem.sendSms(number, message)\n            self.assertIsInstance(sms, gsmmodem.modem.SentSms)\n            self.assertEqual(sms.number, number, 'Sent SMS has invalid number. Expected \"{0}\", got \"{1}\"'.format(number, sms.number))\n            self.assertEqual(sms.text, message, 'Sent SMS has invalid text. Expected \"{0}\", got \"{1}\"'.format(message, sms.text))\n            self.assertIsInstance(sms.reference, int, 'Sent SMS reference type incorrect. Expected \"{0}\", got \"{1}\"'.format(int, type(sms.reference)))\n            self.assertEqual(sms.reference, ref, 'Sent SMS reference incorrect. Expected \"{0}\", got \"{1}\"'.format(ref, sms.reference))\n            self.assertEqual(sms.status, gsmmodem.modem.SentSms.ENROUTE, 'Sent SMS status should have been {0} (\"ENROUTE\"), but is: {1}'.format(gsmmodem.modem.SentSms.ENROUTE, sms.status))\n        self.modem.close()\n\n    def test_sendSmsPduMode(self):\n        \"\"\" Tests sending a SMS messages in PDU mode \"\"\"\n        self.initModem(None)\n        self.modem.smsTextMode = False # Set modem to PDU mode\n        self.modem._smsEncoding = \"GSM\"\n        self.assertFalse(self.modem.smsTextMode)\n        self.firstSMS = True\n        for number, message, index, smsTime, smsc, pdu, sms_deliver_tpdu_length, ref, mem in self.tests:\n            self.modem._smsRef = ref\n            calcPdu = gsmmodem.pdu.encodeSmsSubmitPdu(number, message, ref)[0]\n            pduHex = codecs.encode(compat.str(calcPdu.data), 'hex_codec').upper()\n            if PYTHON_VERSION >= 3:\n                pduHex = str(pduHex, 'ascii')\n\n            def writeCallbackFunc(data):\n                def writeCallbackFuncReadCSCS(data):\n                    self.assertEqual('AT+CSCS=?\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS=?', data))\n                    self.firstSMS = False\n\n                def writeCallbackFunc2(data):\n                    self.assertEqual('AT+CMGS={0}\\r'.format(calcPdu.tpduLength), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGS={0}'.format(calcPdu.tpduLength), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n                    self.modem.serial.flushResponseSequence = False\n                    self.modem.serial.responseSequence = ['> \\r\\n', '+CMGS: {0}\\r\\n'.format(ref), 'OK\\r\\n']\n\n                def writeCallbackFunc3(data):\n                    self.assertEqual('{0}{1}'.format(pduHex, chr(26)), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('{0}{1}'.format(pduHex, chr(26)), data))\n                    self.modem.serial.flushResponseSequence = True\n\n                if self.firstSMS:\n                    return writeCallbackFuncReadCSCS(data)\n                self.assertEqual('AT+CSCS=\"{0}\"\\r'.format(self.modem._smsEncoding), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS=\"{0}\"'.format(self.modem._smsEncoding), data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            sms = self.modem.sendSms(number, message)\n            self.assertIsInstance(sms, gsmmodem.modem.SentSms)\n            self.assertEqual(sms.number, number, 'Sent SMS has invalid number. Expected \"{0}\", got \"{1}\"'.format(number, sms.number))\n            self.assertEqual(sms.text, message, 'Sent SMS has invalid text. Expected \"{0}\", got \"{1}\"'.format(message, sms.text))\n            self.assertIsInstance(sms.reference, int, 'Sent SMS reference type incorrect. Expected \"{0}\", got \"{1}\"'.format(int, type(sms.reference)))\n            self.assertEqual(sms.reference, ref, 'Sent SMS reference incorrect. Expected \"{0}\", got \"{1}\"'.format(ref, sms.reference))\n            self.assertEqual(sms.status, gsmmodem.modem.SentSms.ENROUTE, 'Sent SMS status should have been {0} (\"ENROUTE\"), but is: {1}'.format(gsmmodem.modem.SentSms.ENROUTE, sms.status))\n        self.modem.close()\n\n    def test_sendSmsResponseMixedWithUnsolictedMessages(self):\n        \"\"\" Tests sending a SMS messages (PDU mode), but with unsolicted messages mixed into the modem responses\n        - the only difference here is that the modem's responseSequence contains unsolicted messages\n        taken from github issue #11\n        \"\"\"\n        self.initModem(None)\n        self.modem.smsTextMode = False # Set modem to PDU mode\n        self.modem._smsEncoding = \"GSM\"\n        self.firstSMS = True\n        for number, message, index, smsTime, smsc, pdu, sms_deliver_tpdu_length, ref, mem in self.tests:\n            self.modem._smsRef = ref\n            calcPdu = gsmmodem.pdu.encodeSmsSubmitPdu(number, message, ref)[0]\n            pduHex = codecs.encode(compat.str(calcPdu.data), 'hex_codec').upper()\n            if PYTHON_VERSION >= 3:\n                pduHex = str(pduHex, 'ascii')\n\n            def writeCallbackFunc(data):\n                def writeCallbackFuncReadCSCS(data):\n                    self.assertEqual('AT+CSCS=?\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS=?', data))\n                    self.firstSMS = False\n\n                def writeCallbackFunc2(data):\n                    self.assertEqual('AT+CMGS={0}\\r'.format(calcPdu.tpduLength), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGS={0}'.format(calcPdu.tpduLength), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n                    self.modem.serial.flushResponseSequence = True\n                    # Note thee +ZDONR and +ZPASR unsolicted messages in the \"response\"\n                    self.modem.serial.responseSequence = ['+ZDONR: \"METEOR\",272,3,\"CS_ONLY\",\"ROAM_OFF\"\\r\\n', '+ZPASR: \"UMTS\"\\r\\n', '> \\r\\n']\n\n                def writeCallbackFunc3(data):\n                    self.assertEqual('{0}{1}'.format(pduHex, chr(26)), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('{0}{1}'.format(pduHex, chr(26)), data))\n                    # Note thee +ZDONR and +ZPASR unsolicted messages in the \"response\"\n                    self.modem.serial.responseSequence =  ['+ZDONR: \"METEOR\",272,3,\"CS_ONLY\",\"ROAM_OFF\"\\r\\n', '+ZPASR: \"UMTS\"\\r\\n', '+ZDONR: \"METEOR\",272,3,\"CS_PS\",\"ROAM_OFF\"\\r\\n', '+ZPASR: \"UMTS\"\\r\\n', '+CMGS: {0}\\r\\n'.format(ref), 'OK\\r\\n']\n\n                if self.firstSMS:\n                    return writeCallbackFuncReadCSCS(data)\n                self.assertEqual('AT+CSCS=\"{0}\"\\r'.format(self.modem._smsEncoding), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CSCS=\"{0}\"'.format(self.modem._smsEncoding), data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            sms = self.modem.sendSms(number, message)\n            self.assertIsInstance(sms, gsmmodem.modem.SentSms)\n            self.assertEqual(sms.number, number, 'Sent SMS has invalid number. Expected \"{0}\", got \"{1}\"'.format(number, sms.number))\n            self.assertEqual(sms.text, message, 'Sent SMS has invalid text. Expected \"{0}\", got \"{1}\"'.format(message, sms.text))\n            self.assertIsInstance(sms.reference, int, 'Sent SMS reference type incorrect. Expected \"{0}\", got \"{1}\"'.format(int, type(sms.reference)))\n            self.assertEqual(sms.reference, ref, 'Sent SMS reference incorrect. Expected \"{0}\", got \"{1}\"'.format(ref, sms.reference))\n        self.modem.close()\n    \n    def test_receiveSmsTextMode(self):\n        \"\"\" Tests receiving SMS messages in text mode \"\"\"\n        callbackInfo = [False, '', '', -1, None, '', None]\n        def smsReceivedCallbackFuncText(sms):\n            try:\n                self.assertIsInstance(sms, gsmmodem.modem.ReceivedSms)\n                self.assertEqual(sms.number, callbackInfo[1], 'SMS sender number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(callbackInfo[1], sms.number))\n                self.assertEqual(sms.text, callbackInfo[2], 'SMS text incorrect. Expected: \"{0}\", got: \"{1}\"'.format(callbackInfo[2], sms.text))\n                self.assertIsInstance(sms.time, datetime, 'SMS received time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.time)))\n                self.assertEqual(sms.time, callbackInfo[4], 'SMS received time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(callbackInfo[4], sms.time))\n                self.assertEqual(sms.status, gsmmodem.modem.Sms.STATUS_RECEIVED_UNREAD)\n                self.assertEqual(sms.smsc, None, 'Text-mode SMS should not have any SMSC information')\n            finally:\n                callbackInfo[0] = True\n\n        self.initModem(smsReceivedCallbackFunc=smsReceivedCallbackFuncText)\n        self.modem.smsTextMode = True # Set modem to text mode\n        self.assertTrue(self.modem.smsTextMode)\n        for number, message, index, smsTime, smsc, pdu, tpdu_length, ref, mem in self.tests:            \n            # Wait for the handler function to finish\n            callbackInfo[0] = False # \"done\" flag\n            callbackInfo[1] = number\n            callbackInfo[2] = message\n            callbackInfo[3] = index\n            callbackInfo[4] = smsTime\n            \n            # Time string as returned by modem in text modem\n            tzDelta = smsTime.utcoffset()\n            if tzDelta.days >= 0:\n                tzValStr = '+{0:0>2}'.format(int(tzDelta.seconds / 60 / 15)) # calculate offset in 0.25 hours\n            if tzDelta.days < 0: # negative\n                tzValStr = '-{0:0>2}'.format(int((tzDelta.days * -3600 * 24 - tzDelta.seconds) / 60 / 15))\n            textModeStr = smsTime.strftime('%y/%m/%d,%H:%M:%S') + tzValStr\n            def writeCallbackFunc(data):\n                \"\"\" Intercept the \"read stored message\" command \"\"\"        \n                def writeCallbackFunc2(data):                    \n                    self.assertEqual('AT+CMGR={0}\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGR={0}'.format(index), data))\n                    self.modem.serial.responseSequence = ['+CMGR: \"REC UNREAD\",\"{0}\",,\"{1}\"\\r\\n'.format(number, textModeStr), '{0}\\r\\n'.format(message), 'OK\\r\\n']\n                    def writeCallbackFunc3(data):\n                        self.assertEqual('AT+CMGD={0},0\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0}'.format(index), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n                if self.modem._smsMemReadDelete != mem:\n                    self.assertEqual('AT+CPMS=\"{0}\"\\r'.format(mem), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CPMS=\"{0}\"'.format(mem), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                else:\n                    # Modem does not need to change read memory\n                    writeCallbackFunc2(data)\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            # Fake a \"new message\" notification\n            self.modem.serial.responseSequence = ['+CMTI: \"{0}\",{1}\\r\\n'.format(mem, index)]\n            # Wait for the handler function to finish\n            while callbackInfo[0] == False:\n                time.sleep(0.1)\n        self.modem.close()\n        \n    def test_receiveSmsPduMode(self):\n        \"\"\" Tests receiving SMS messages in PDU mode \"\"\"\n        callbackInfo = [False, '', '', -1, None, '', None]\n        def smsReceivedCallbackFuncPdu(sms):\n            try:\n                self.assertIsInstance(sms, gsmmodem.modem.ReceivedSms)\n                self.assertEqual(sms.number, callbackInfo[1], 'SMS sender number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(callbackInfo[1], sms.number))\n                self.assertEqual(sms.text, callbackInfo[2], 'SMS text incorrect. Expected: \"{0}\", got: \"{1}\"'.format(callbackInfo[2], sms.text))\n                self.assertIsInstance(sms.time, datetime, 'SMS received time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.time)))\n                self.assertEqual(sms.time, callbackInfo[4], 'SMS received time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(callbackInfo[4], sms.time))\n                self.assertEqual(sms.status, gsmmodem.modem.Sms.STATUS_RECEIVED_UNREAD)\n                self.assertEqual(sms.smsc, callbackInfo[5], 'PDU-mode SMS SMSC number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(callbackInfo[5], sms.smsc))\n            finally:\n                callbackInfo[0] = True\n\n        self.initModem(smsReceivedCallbackFunc=smsReceivedCallbackFuncPdu)\n        self.modem.smsTextMode = False # Set modem to PDU mode\n        self.assertFalse(self.modem.smsTextMode)\n        for pduAddressText in self.testsPduAddressText:\n            for number, message, index, smsTime, smsc, pdu, tpdu_length, ref, mem in self.tests:\n                if smsc == None or pdu == None:\n                    continue # not enough info for a PDU test, skip it\n                # Wait for the handler function to finish\n                callbackInfo[0] = False # \"done\" flag\n                callbackInfo[1] = number\n                callbackInfo[2] = message\n                callbackInfo[3] = index\n                callbackInfo[4] = smsTime\n                callbackInfo[5] = smsc\n            \n                def writeCallbackFunc(data):\n                    def writeCallbackFunc2(data):\n                        \"\"\" Intercept the \"read stored message\" command \"\"\"\n                        self.assertEqual('AT+CMGR={0}\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGR={0}'.format(index), data))\n                        self.modem.serial.responseSequence = ['+CMGR: 0,{0},{1}\\r\\n'.format(pduAddressText, tpdu_length), '{0}\\r\\n'.format(pdu), 'OK\\r\\n']                \n                        def writeCallbackFunc3(data):\n                            self.assertEqual('AT+CMGD={0},0\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0}'.format(index), data))\n                        self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n                    if self.modem._smsMemReadDelete != mem:\n                        self.assertEqual('AT+CPMS=\"{0}\"\\r'.format(mem), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CPMS=\"{0}\"'.format(mem), data))\n                        self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                    else:\n                        # Modem does not need to change read memory\n                        writeCallbackFunc2(data)\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc\n                # Fake a \"new message\" notification\n                self.modem.serial.responseSequence = ['+CMTI: \"SM\",{0}\\r\\n'.format(index)]\n                # Wait for the handler function to finish\n                while callbackInfo[0] == False:\n                    time.sleep(0.1)\n        self.modem.close()\n\n    def test_sendSms_refCount(self):\n        \"\"\" Test the SMS reference counter operation when sending SMSs \"\"\"\n        self.initModem(None)\n        \n        ref = 0\n        def writeCallbackFunc(data):\n            if data.startswith('AT+CMGS'):\n                self.modem.serial.flushResponseSequence = False\n                self.modem.serial.responseSequence = ['> \\r\\n', '+CMGS: {0}\\r\\n'.format(ref), 'OK\\r\\n']\n            else:\n                self.modem.serial.flushResponseSequence = True\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        \n        ref = 0\n        sms = self.modem.sendSms(\"+27820000000\", 'Test message')\n        firstRef = sms.reference\n        self.assertEqual(firstRef, 0)\n        # Ensure the reference counter is incremented each time an SMS is sent\n        ref = 1\n        sms = self.modem.sendSms(\"+27820000000\", 'Test message 2')\n        reference = sms.reference\n        self.assertEqual(sms.reference, firstRef + 1)\n        # Ensure the reference counter rolls over once 255 is reached\n        ref = 255\n        self.modem._smsRef = 255\n        sms = self.modem.sendSms(\"+27820000000\", 'Test message 3')\n        ref = 0\n        self.assertEqual(sms.reference, 255)\n        sms = self.modem.sendSms(\"+27820000000\", 'Test message 4')\n        self.assertEqual(sms.reference, 0)\n        self.modem.close()\n    \n    def test_sendSms_waitForDeliveryReport(self):\n        \"\"\" Test waiting for the status report when sending SMSs \"\"\"\n        self.initModem(None)\n        causeTimeout = [False]\n        def writeCallbackFunc(data):\n            if data.startswith('AT+CMGS'):\n                self.modem.serial.flushResponseSequence = False\n                if causeTimeout[0]:\n                    self.modem.serial.responseSequence = ['> \\r\\n', '+CMGS: 183\\r\\n', 'OK\\r\\n']\n                else:\n                    # Fake a delivery report notification after sending SMS\n                    self.modem.serial.responseSequence = ['> \\r\\n', '+CMGS: 183\\r\\n', 'OK\\r\\n', 0.1, '+CDSI: \"SM\",3\\r\\n']\n            elif data.startswith('AT+CMGR'):\n                # Provide a fake status report - these are tested by the TestSmsStatusReports class\n                self.modem.serial.responseSequence = ['+CMGR: 0,,24\\r\\n', '07917248014000F506B70AA18092020000317071518590803170715185418000\\r\\n', 'OK\\r\\n']\n            else:\n                self.modem.serial.flushResponseSequence = True\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        # Prepare send SMS response as well as \"delivered\" notification\n        self.modem._smsRef = 183\n        sms = self.modem.sendSms('0829200000', 'Test message', waitForDeliveryReport=True)\n        self.assertIsInstance(sms, gsmmodem.modem.SentSms)\n        self.assertNotEqual(sms.report, None, 'Sent SMS\\'s \"report\" attribute should not be None')\n        self.assertIsInstance(sms.report, gsmmodem.modem.StatusReport)\n        self.assertEqual(sms.status, gsmmodem.modem.SentSms.DELIVERED, 'Sent SMS status should have been {0} (\"DELIVERED\"), but is: {1}'.format(gsmmodem.modem.SentSms.DELIVERED, sms.status))\n        # Now test timeout event when waiting for delivery report\n        causeTimeout[0] = True\n        self.modem._smsRef = 183\n        # Set deliveryTimeout to 0.05 - should timeout very quickly\n        self.assertRaises(gsmmodem.exceptions.TimeoutException, self.modem.sendSms, **{'destination': '0829200000', 'text': 'Test message', 'waitForDeliveryReport': True, 'deliveryTimeout': 0.05})\n        self.modem.close()\n    \n    def test_sendSms_reply(self):\n        \"\"\" Test the reply() method of the ReceivedSms class \"\"\"\n        self.initModem(None)\n        \n        def writeCallbackFunc(data):\n            if data.startswith('AT+CMGS'):\n                self.modem.serial.flushResponseSequence = False\n                self.modem.serial.responseSequence = ['> \\r\\n', '+CMGS: 0\\r\\n', 'OK\\r\\n']\n            else:\n                self.modem.serial.flushResponseSequence = True\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        \n        receivedSms = gsmmodem.modem.ReceivedSms(self.modem, gsmmodem.modem.ReceivedSms.STATUS_RECEIVED_READ, '+27820000000', datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)), 'Text message', '+9876543210')\n        sms = receivedSms.reply('This is the reply')\n        self.assertIsInstance(sms, gsmmodem.modem.SentSms)\n        self.assertEqual(sms.number, receivedSms.number)\n        self.assertEqual(sms.text, 'This is the reply')\n        self.modem.close()\n        \n    def test_sendSms_noCgmsResponse(self):\n        \"\"\" Test GsmModem.sendSms() but issue an invalid response from the modem \"\"\"\n        self.initModem(None)\n        # Modem is just going to respond with \"OK\" to the send SMS command\n        self.assertRaises(gsmmodem.exceptions.CommandError, self.modem.sendSms, '+27820000000', 'Test message')\n        self.modem.close()\n\nclass TestStoredSms(unittest.TestCase):\n    \"\"\" Tests processing/accessing SMS messages stored on the SIM card \"\"\"\n    \n    def initModem(self, textMode, smsReceivedCallbackFunc):\n        global FAKE_MODEM\n        # Override the pyserial import\n        mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = mockSerial\n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --', smsReceivedCallbackFunc=smsReceivedCallbackFunc)\n        self.modem.smsTextMode = textMode\n        self.modem.connect()\n        FAKE_MODEM = None\n    \n    def setUp(self):\n        self.modem = None\n    \n    def tearDown(self):\n        if self.modem != None:\n            self.modem.close()\n    \n    def initFakeModemResponses(self, textMode):\n        global FAKE_MODEM\n        FAKE_MODEM = copy(fakemodems.GenericTestModem())\n        modem = gsmmodem.modem.GsmModem('--weak ref object--')\n        self.expectedMessages = [ReceivedSms(modem, Sms.STATUS_RECEIVED_UNREAD, '+27748577604', datetime(2013, 1, 28, 14, 51, 42, tzinfo=SimpleOffsetTzInfo(2)), 'Hello raspberry pi', None),\n                                 ReceivedSms(modem, Sms.STATUS_RECEIVED_READ, '+2784000153099999', datetime(2013, 2, 7, 1, 31, 44, tzinfo=SimpleOffsetTzInfo(2)), 'New and here to stay! Don\\'t just recharge SUPACHARGE and get your recharged airtime+FREE CellC to CellC mins & SMSs+Free data to use anytime. T&C apply. Cell C', None),\n                                 ReceivedSms(modem, Sms.STATUS_RECEIVED_READ, '+27840001463', datetime(2013, 2, 7, 6, 24, 2, tzinfo=SimpleOffsetTzInfo(2)), 'Standard Bank: Your accounts are no longer FICA compliant. Please bring ID & proof of residence to any branch to reactivate your accounts. Queries? 0860003422.')]       \n        if textMode:\n            FAKE_MODEM.responses['AT+CMGL=\"REC UNREAD\"\\r'] = ['+CMGL: 0,\"REC UNREAD\",\"+27748577604\",,\"13/01/28,14:51:42+08\"\\r\\n', 'Hello raspberry pi\\r\\n',\n                                                              'OK\\r\\n']\n            FAKE_MODEM.responses['AT+CMGL=\"REC READ\"\\r'] = ['+CMGL: 1,\"REC READ\",\"+2784000153099999\",,\"13/02/07,01:31:44+08\"\\r\\n', 'New and here to stay! Don\\'t just recharge SUPACHARGE and get your recharged airtime+FREE CellC to CellC mins & SMSs+Free data to use anytime. T&C apply. Cell C\\r\\n',\n                                                            '+CMGL: 2,\"REC READ\",\"+27840001463\",,\"13/02/07,06:24:02+08\"\\r\\n', 'Standard Bank: Your accounts are no longer FICA compliant. Please bring ID & proof of residence to any branch to reactivate your accounts. Queries? 0860003422.\\r\\n',\n                                                            'OK\\r\\n']\n            allMessages = FAKE_MODEM.responses['AT+CMGL=\"REC UNREAD\"\\r'][:-1]\n            allMessages.extend(FAKE_MODEM.responses['AT+CMGL=\"REC READ\"\\r'])\n            FAKE_MODEM.responses['AT+CMGL=\"ALL\"\\r'] = allMessages\n            FAKE_MODEM.responses['AT+CMGL=\"STO UNSENT\"\\r'] = FAKE_MODEM.responses['AT+CMGL=\"STO SENT\"\\r'] = ['OK\\r\\n']\n            FAKE_MODEM.responses['AT+CMGL=0\\r'] = FAKE_MODEM.responses['AT+CMGL=1\\r'] = FAKE_MODEM.responses['AT+CMGL=2\\r'] = FAKE_MODEM.responses['AT+CMGL=3\\r'] = FAKE_MODEM.responses['AT+CMGL=4\\r'] = ['ERROR\\r\\n']\n        else:\n            FAKE_MODEM.responses['AT+CMGL=0\\r'] = ['+CMGL: 0,0,,35\\r\\n', '07917248014000F3240B917247587706F400003110824115248012C8329BFD06C9C373B8B82C97E741F034\\r\\n',\n                                                   'OK\\r\\n'] \n            FAKE_MODEM.responses['AT+CMGL=1\\r'] = ['+CMGL: 1,1,,161\\r\\n', '07917248010080F020109172480010359099990000312070101344809FCEF21D14769341E8B2BC0CA2BF41737A381F0211DFEE131DA4AECFE92079798C0ECBCF65D0B40A0D0E9141E9B1080ABBC9A073990ECABFEB7290BC3C4687E5E73219144ECBE9E976796594168BA06199CD1E82E86FD0B0CC660F41EDB47B0E3281A6CDE97C659497CB2072981E06D1DFA0FABC0C0ABBF3F474BBEC02514D4350180E67E75DA06199CD060D01\\r\\n',\n                                                   '+CMGL: 2,1,,159\\r\\n', '07917248010080F0240B917248001064F30000312070604220809F537AD84D0ECBC92061D8BDD681B2EFBA1C141E8FDF75377D0E0ACBCB20F71BC47EBBCF6539C8981C0641E3771BCE4E87DD741708CA2E87E76590589E769F414922C80482CBDF6F33E86D06C9CBF334B9EC1E9741F43728ECCE83C4F2B07B8C06D1DF2079393CA6A7ED617A19947FD7E5A0F078FCAEBBE97317285A2FCBD3E5F90F04C3D96030D88C2693B900\\r\\n',\n                                                   'OK\\r\\n']\n            allMessages = FAKE_MODEM.responses['AT+CMGL=0\\r'][:-1]\n            allMessages.extend(FAKE_MODEM.responses['AT+CMGL=1\\r'])\n            FAKE_MODEM.responses['AT+CMGL=4\\r'] = allMessages\n            FAKE_MODEM.responses['AT+CMGL=2\\r'] = FAKE_MODEM.responses['AT+CMGL=3\\r'] = ['OK\\r\\n']\n            FAKE_MODEM.responses['AT+CMGL=\"REC UNREAD\"\\r'] = FAKE_MODEM.responses['AT+CMGL=\"REC READ\"\\r'] = FAKE_MODEM.responses['AT+CMGL=\"STO UNSENT\"\\r'] = FAKE_MODEM.responses['AT+CMGL=\"STO SENT\"\\r'] = FAKE_MODEM.responses['AT+CMGL=\"ALL\"\\r'] = ['ERROR\\r\\n']\n            FAKE_MODEM.responses['AT+CMGR=0\\r'] = ['+CMGR: 0,,35\\r\\n', '07917248014000F3240B917247587706F400003110824115248012C8329BFD06C9C373B8B82C97E741F034\\r\\n', 'OK\\r\\n']\n\n    def test_listStoredSms_pdu(self):\n        \"\"\" Tests listing/reading SMSs that are currently stored on the SIM card (PDU mode) \"\"\"\n        self.initFakeModemResponses(textMode=False)\n        self.initModem(False, None)\n        # Test getting all messages\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CMGL=4\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGL=4', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        messages = self.modem.listStoredSms()\n        self.assertIsInstance(messages, list)\n        self.assertEqual(len(messages), 3, 'Invalid number of messages returned; expected 3, got {0}'.format(len(messages)))\n        \n        for i in range(len(messages)):\n            message = messages[i]\n            expected = self.expectedMessages[i]\n            self.assertIsInstance(message, expected.__class__)\n            self.assertEqual(message.number, expected.number)\n            self.assertEqual(message.status, expected.status)\n            self.assertEqual(message.text, expected.text)\n            self.assertEqual(message.time, expected.time)\n        del messages\n        \n        # Test filtering\n        tests = ((Sms.STATUS_RECEIVED_UNREAD, 1), (Sms.STATUS_RECEIVED_READ, 2), (Sms.STATUS_STORED_SENT, 0), (Sms.STATUS_STORED_UNSENT, 0))\n        for status, numberOfMessages in tests:\n            def writeCallbackFunc2(data):\n                self.assertEqual('AT+CMGL={0}\\r'.format(status), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGL={0}'.format(status), data))\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n            messages = self.modem.listStoredSms(status=status)\n            self.assertIsInstance(messages, list)\n            self.assertEqual(len(messages), numberOfMessages, 'Invalid number of messages returned for status: {0}; expected {1}, got {2}'.format(status, numberOfMessages, len(messages)))        \n            del messages\n        \n        # Test deleting messages after retrieval\n        # Test deleting all messages\n        expectedFilter = [4, ['1,4']]\n        delCount = [0]\n        def writeCallbackFunc3(data):\n            self.assertEqual('AT+CMGL={0}\\r'.format(expectedFilter[0]), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGL={0}'.format(expectedFilter[0]), data))\n            def writeCallbackFunc4(data):\n                self.assertEqual('AT+CMGD={0}\\r'.format(expectedFilter[1][delCount[0]]), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0}'.format(expectedFilter[1][delCount[0]]), data))\n                delCount[0] += 1\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc4\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n        messages = self.modem.listStoredSms(status=Sms.STATUS_ALL, delete=True)\n        self.assertIsInstance(messages, list)\n        self.assertEqual(len(messages), 3, 'Invalid number of messages returned; expected 3, got {0}'.format(len(messages)))\n        \n        # Test deleting filtered messages\n        expectedFilter[0] = 1\n        expectedFilter[1] = ['1,0', '2,0']\n        delCount[0] = 0\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n        messages = self.modem.listStoredSms(status=Sms.STATUS_RECEIVED_READ, delete=True)\n        \n        # Test error handling if an invalid line is added between PDU data (line should be ignored)\n        self.modem.serial.writeCallbackFunc = None\n        self.modem.serial.modem.responses['AT+CMGL=4\\r'].insert(1, 'AFSDLF SDKFJSKDLFJLKSDJF SJDLKFSKLDJFKSDFS\\r\\n')\n        messages = self.modem.listStoredSms()\n        self.assertIsInstance(messages, list)\n        self.assertEqual(len(messages), 3, 'Invalid number of messages returned; expected 3, got {0}'.format(len(messages)))\n\n    def test_listStoredSms_text(self):\n        \"\"\" Tests listing/reading SMSs that are currently stored on the SIM card (text mode) \"\"\"\n        self.initFakeModemResponses(textMode=True)\n        self.initModem(True, None)\n        \n        # Test getting all messages\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CMGL=\"ALL\"\\r', data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGL=\"ALL\"', data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        messages = self.modem.listStoredSms()\n        self.assertIsInstance(messages, list)\n        self.assertEqual(len(messages), 3, 'Invalid number of messages returned; expected 3, got {0}'.format(len(messages)))\n        \n        for i in range(len(messages)):\n            message = messages[i]\n            expected = self.expectedMessages[i]\n            self.assertIsInstance(message, expected.__class__)\n            self.assertEqual(message.number, expected.number)\n            self.assertEqual(message.status, expected.status)\n            self.assertEqual(message.text, expected.text)\n            self.assertEqual(message.time, expected.time)\n        del messages\n        \n        # Test filtering\n        tests = ((Sms.STATUS_RECEIVED_UNREAD, 'REC UNREAD', 1), (Sms.STATUS_RECEIVED_READ, 'REC READ', 2), (Sms.STATUS_STORED_SENT, 'STO SENT', 0), (Sms.STATUS_STORED_UNSENT, 'STO UNSENT', 0))\n        for status, statusStr, numberOfMessages in tests:\n            def writeCallbackFunc2(data):\n                self.assertEqual('AT+CMGL=\"{0}\"\\r'.format(statusStr), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGL=\"{0}\"'.format(statusStr), data))\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n            messages = self.modem.listStoredSms(status=status)\n            self.assertIsInstance(messages, list)\n            self.assertEqual(len(messages), numberOfMessages, 'Invalid number of messages returned for status: {0}; expected {1}, got {2}'.format(status, numberOfMessages, len(messages)))\n            del messages\n        \n        # Test deleting messages after retrieval\n        # Test deleting all messages\n        expectedFilter = ['ALL', ['1,4']]\n        delCount = [0]\n        def writeCallbackFunc3(data):\n            self.assertEqual('AT+CMGL=\"{0}\"\\r'.format(expectedFilter[0]), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGL=\"{0}\"'.format(expectedFilter[0]), data))\n            def writeCallbackFunc4(data):\n                self.assertEqual('AT+CMGD={0}\\r'.format(expectedFilter[1][delCount[0]]), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0}'.format(expectedFilter[1][delCount[0]]), data))\n                delCount[0] += 1\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc4\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n        messages = self.modem.listStoredSms(status=Sms.STATUS_ALL, delete=True)\n        self.assertIsInstance(messages, list)\n        self.assertEqual(len(messages), 3, 'Invalid number of messages returned; expected 3, got {0}'.format(len(messages)))\n        \n        # Test deleting filtered messages\n        expectedFilter[0] = 'REC READ'\n        expectedFilter[1] = ['1,0', '2,0']\n        delCount[0] = 0\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n        messages = self.modem.listStoredSms(status=Sms.STATUS_RECEIVED_READ, delete=True)\n        \n        # Test error handling when specifying an invalid SMS status value\n        self.modem.serial.writeCallbackFunc = None\n        self.assertRaises(ValueError, self.modem.listStoredSms, **{'status': 99})\n    \n    def test_processStoredSms(self):\n        \"\"\" Tests processing and then \"receiving\" SMSs that are currently stored on the SIM card \"\"\"\n        self.initFakeModemResponses(textMode=False)\n        \n        expectedMessages = copy(self.expectedMessages)\n        unread = expectedMessages.pop(0)\n        expectedMessages.append(unread)\n        \n        i = [0]\n        def smsCallbackFunc(sms):\n            expected = expectedMessages[i[0]]\n            self.assertIsInstance(sms, ReceivedSms)\n            self.assertEqual(sms.number, expected.number)\n            self.assertEqual(sms.status, expected.status)\n            self.assertEqual(sms.text, expected.text)\n            self.assertEqual(sms.time, expected.time)\n            i[0] += 1\n        \n        self.initModem(False, smsCallbackFunc)\n        \n        commandsWritten = [False, False]\n        def writeCallbackFunc(data):\n            if data.startswith('AT+CMGL'):\n                commandsWritten[0] = True\n            elif data.startswith('AT+CMGD'):\n                commandsWritten[1] = True\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        \n        self.modem.processStoredSms()\n        self.assertTrue(commandsWritten[0], 'AT+CMGL command not written to modem')\n        self.assertTrue(commandsWritten[1], 'AT+CMGD command not written to modem')\n        self.assertEqual(i[0], 3, 'Message received callback count incorrect; expected 3, got {0}'.format(i[0]))\n        \n        # Test unread only\n        commandsWritten[0] = commandsWritten[1] = False\n        i[0] = 0\n        expectedMessages = [unread]\n        self.modem.processStoredSms(unreadOnly=True)\n        self.assertTrue(commandsWritten[0], 'AT+CMGL command not written to modem')\n        self.assertTrue(commandsWritten[1], 'AT+CMGD command not written to modem')\n        self.assertEqual(i[0], 1, 'Message received callback count incorrect; expected 1, got {0}'.format(i[0]))\n    \n    def test_deleteStoredSms(self):\n        self.initFakeModemResponses(textMode=True)\n        self.initModem(True, None)\n        \n        tests = (1,2,3)\n        for index in tests:        \n            def writeCallbackFunc(data):\n                self.assertEqual('AT+CMGD={0},0\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0},0'.format(index), data))\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.modem.deleteStoredSms(index)\n        # Test switching SMS memory\n        tests = ((5, 'TEST1'), (32, 'ME'))\n        for index, mem in tests:\n            def writeCallbackFunc(data):\n                self.assertEqual('AT+CPMS=\"{0}\"\\r'.format(mem), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CPMS=\"{0}\"'.format(mem), data))\n                def writeCallbackFunc2(data):\n                    self.assertEqual('AT+CMGD={0},0\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0},0'.format(index), data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.modem.deleteStoredSms(index, memory=mem)\n            \n    def test_deleteMultipleStoredSms(self):\n        self.initFakeModemResponses(textMode=True)\n        self.initModem(True, None)\n        \n        tests = (4,3,2,1)\n        for delFlag in tests:        \n            # Test getting all messages\n            def writeCallbackFunc(data):\n                self.assertEqual('AT+CMGD=1,{0}\\r'.format(delFlag), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD=1,{0}'.format(delFlag), data))\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.modem.deleteMultipleStoredSms(delFlag)\n        # Test switching SMS memory\n        tests = ((4, 'TEST1'), (4, 'ME'))\n        for delFlag, mem in tests:\n            def writeCallbackFunc(data):\n                self.assertEqual('AT+CPMS=\"{0}\"\\r'.format(mem), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CPMS=\"{0}\"'.format(mem), data))\n                def writeCallbackFunc2(data):\n                    self.assertEqual('AT+CMGD=1,{0}\\r'.format(delFlag), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD=1,{0}'.format(delFlag), data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.modem.deleteMultipleStoredSms(delFlag, memory=mem)\n        # Test default delFlag value\n        delFlag = 4\n        def writeCallbackFunc3(data):\n            self.assertEqual('AT+CMGD=1,{0}\\r'.format(delFlag), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD=1,{0}'.format(delFlag), data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n        self.modem.deleteMultipleStoredSms()\n        # Test invalid delFlag values\n        tests = (0, 5, -3)\n        for delFlag in tests:\n            self.assertRaises(ValueError, self.modem.deleteMultipleStoredSms, **{'delFlag': delFlag})\n    \n    def test_readStoredSms_pdu(self):\n        \"\"\" Tests reading stored SMS messages (PDU mode) \"\"\"\n        self.initFakeModemResponses(textMode=False)\n        self.initModem(False, None)\n        \n        # Test basic reading\n        index = 0\n        def writeCallbackFunc(data):\n            self.assertEqual('AT+CMGR={0}\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGR={0}'.format(index), data))\n        self.modem.serial.writeCallbackFunc = writeCallbackFunc\n        message = self.modem.readStoredSms(index)\n        expected = self.expectedMessages[index]\n        self.assertIsInstance(message, expected.__class__)\n        self.assertEqual(message.number, expected.number)\n        self.assertEqual(message.status, expected.status)\n        self.assertEqual(message.text, expected.text)\n        self.assertEqual(message.time, expected.time)\n        \n        # Test switching SMS memory\n        tests = ((0, 'TEST1'), (0, 'ME'))\n        for index, mem in tests:\n            def writeCallbackFunc(data):\n                self.assertEqual('AT+CPMS=\"{0}\"\\r'.format(mem), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CPMS=\"{0}\"'.format(mem), data))\n                def writeCallbackFunc2(data):\n                    self.assertEqual('AT+CMGR={0}\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGR={0}'.format(index), data))\n                self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            self.modem.readStoredSms(index, memory=mem)\n            expected = self.expectedMessages[index]\n            self.assertIsInstance(message, expected.__class__)\n            self.assertEqual(message.number, expected.number)\n            self.assertEqual(message.status, expected.status)\n            self.assertEqual(message.text, expected.text)\n            self.assertEqual(message.time, expected.time)\n\n\nclass TestSmsStatusReports(unittest.TestCase):\n    \"\"\" Tests receiving SMS status reports \"\"\"\n        \n    def initModem(self, smsStatusReportCallback):\n        # Override the pyserial import        \n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.modem = gsmmodem.modem.GsmModem('-- PORT IGNORED DURING TESTS --', smsStatusReportCallback=smsStatusReportCallback)        \n        self.modem.connect()\n    \n    def test_receiveStatusReportTextMode(self):\n        \"\"\" Tests receiving SMS status reports in text mode \"\"\"\n        \n        tests = ((57, 'SR',\n                  '+CMGR: ,6,20,\"0870000000\",129,\"13/04/29,19:58:00+04\",\"13/04/29,19:59:00+04\",0',\n                  Sms.STATUS_RECEIVED_UNREAD, # message read status \n                  '0870000000', # number\n                  20, # reference\n                  datetime(2013, 4, 29, 19, 58, 0, tzinfo=SimpleOffsetTzInfo(1)), # sentTime\n                  datetime(2013, 4, 29, 19, 59, 0, tzinfo=SimpleOffsetTzInfo(1)), # deliverTime\n                  StatusReport.DELIVERED), # delivery status\n                 )\n        \n        callbackDone = [False]\n        \n        for index, mem, notification, msgStatus, number, reference, sentTime, deliverTime, deliveryStatus in tests:            \n            def smsStatusReportCallbackFuncText(sms):\n                try:\n                    self.assertIsInstance(sms, gsmmodem.modem.StatusReport)\n                    self.assertEqual(sms.status, msgStatus, 'Status report read status incorrect. Expected: \"{0}\", got: \"{1}\"'.format(msgStatus, sms.status))\n                    self.assertEqual(sms.number, number, 'SMS sender number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(number, sms.number))                    \n                    self.assertEqual(sms.reference, reference, 'Status report SMS reference number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(reference, sms.reference))\n                    self.assertIsInstance(sms.timeSent, datetime, 'SMS sent time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.timeSent)))\n                    self.assertEqual(sms.timeSent, sentTime, 'SMS sent time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(sentTime, sms.timeSent))\n                    self.assertIsInstance(sms.timeFinalized, datetime, 'SMS finalized time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.timeFinalized)))\n                    self.assertEqual(sms.timeFinalized, deliverTime, 'SMS finalized time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(deliverTime, sms.timeFinalized))\n                    self.assertEqual(sms.deliveryStatus, deliveryStatus, 'SMS delivery status incorrect. Expected: \"{0}\", got: \"{1}\"'.format(deliveryStatus, sms.deliveryStatus))                \n                    self.assertEqual(sms.smsc, None, 'Text-mode SMS should not have any SMSC information')\n                finally:\n                    callbackDone[0] = True\n            self.initModem(smsStatusReportCallback=smsStatusReportCallbackFuncText)\n            self.modem.smsTextMode = True\n            def writeCallbackFunc(data):\n                def writeCallbackFunc2(data):                    \n                    self.assertEqual('AT+CMGR={0}\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGR={0}'.format(index), data))\n                    self.modem.serial.responseSequence = ['{0}\\r\\n'.format(notification), 'OK\\r\\n']\n                    def writeCallbackFunc3(data):\n                        self.assertEqual('AT+CMGD={0},0\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0}'.format(index), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n                if self.modem._smsMemReadDelete != mem:\n                    self.assertEqual('AT+CPMS=\"{0}\"\\r'.format(mem), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CPMS=\"{0}\"'.format(mem), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                else:\n                    # Modem does not need to change read memory\n                    writeCallbackFunc2(data)\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            # Fake a \"new status report\" notification\n            self.modem.serial.responseSequence = ['+CDSI: \"{0}\",{1}\\r\\n'.format(mem, index)]\n            # Wait for the handler function to finish\n            while callbackDone[0] == False:\n                time.sleep(0.1)\n        self.modem.close()\n        \n    def test_receiveSmsPduMode_problemCases(self):\n        \"\"\" Test receiving PDU-mode SMS using data captured from failed operations/bug reports \"\"\"\n        # AT+CMGR response from ZTE modem breaks incoming message read - simply test that we can parse it properly\n        zteResponse = ['+CMGR: ,,27\\r\\n', '0297F1061C0F910B487228297020F5317062419272803170624192138000\\r\\n', 'OK\\r\\n']\n        \n        callbackInfo = [False, '', '', -1, None, '', None]\n        def smsCallbackFunc1(sms):\n            try:\n                self.assertIsInstance(sms, gsmmodem.modem.StatusReport)\n                # Since the +CMGR response did not include the SMS's status, see if the default fallback was loaded correctly\n                self.assertEqual(sms.status, gsmmodem.modem.Sms.STATUS_RECEIVED_UNREAD)\n            finally:\n                callbackInfo[0] = True\n        \n        def writeCallback1(data):\n            if data.startswith('AT+CMGR'):\n                self.modem.serial.flushResponseSequence = True\n                self.modem.serial.responseSequence = zteResponse\n\n        self.initModem(smsStatusReportCallback=smsCallbackFunc1)\n        # Fake a \"new message\" notification\n        self.modem.serial.writeCallbackFunc = writeCallback1\n        self.modem.serial.responseSequence = ['+CDSI: \"SM\",1\\r\\n']\n        # Wait for the handler function to finish\n        while callbackInfo[0] == False:\n            time.sleep(0.1)\n        \n    def test_receiveStatusReportPduMode(self):\n        \"\"\" Tests receiving SMS status reports in PDU mode \"\"\"\n        tests = ((3, 'SM',\n                  ['+CMGR: 0,,24\\r\\n', '07917248014000F506B70AA18092020000317071518590803170715185418000\\r\\n', 'OK\\r\\n'],\n                  Sms.STATUS_RECEIVED_UNREAD, # message read status \n                  '0829200000', # number\n                  183, # reference\n                  datetime(2013, 7, 17, 15, 58, 9, tzinfo=SimpleOffsetTzInfo(2)), # sentTime\n                  datetime(2013, 7, 17, 15, 58, 14, tzinfo=SimpleOffsetTzInfo(2)), # deliverTime\n                  StatusReport.DELIVERED), # delivery status\n                 (1, 'SM', # This output was captured from a ZTE modem that seems to be broken (PDU is semi-invalid (SMSC length incorrect), and +CMGR output missing status)\n                  ['+CMGR: ,,27\\r\\n', '0297F1061C0F910B487228297020F5317062419272803170624192138000\\r\\n', 'OK\\r\\n'],\n                  Sms.STATUS_RECEIVED_UNREAD,\n                  '+b08427829207025', # <-- note the broken number\n                  28,\n                  datetime(2013, 7, 26, 14, 29, 27, tzinfo=SimpleOffsetTzInfo(2)), # sentTime\n                  datetime(2013, 7, 26, 14, 29, 31, tzinfo=SimpleOffsetTzInfo(2)), # deliverTime\n                  StatusReport.DELIVERED),\n                 )\n        \n        callbackDone = [False]\n        \n        for index, mem, responseSeq, msgStatus, number, reference, sentTime, deliverTime, deliveryStatus in tests:\n            callbackDone[0] = False\n            def smsStatusReportCallbackFuncText(sms):\n                try:\n                    self.assertIsInstance(sms, gsmmodem.modem.StatusReport)\n                    self.assertEqual(sms.status, msgStatus, 'Status report read status incorrect. Expected: \"{0}\", got: \"{1}\"'.format(msgStatus, sms.status))\n                    self.assertEqual(sms.number, number, 'SMS sender number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(number, sms.number))\n                    self.assertEqual(sms.reference, reference, 'Status report SMS reference number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(reference, sms.reference))\n                    self.assertIsInstance(sms.timeSent, datetime, 'SMS sent time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.timeSent)))\n                    self.assertEqual(sms.timeSent, sentTime, 'SMS sent time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(sentTime, sms.timeSent))\n                    self.assertIsInstance(sms.timeFinalized, datetime, 'SMS finalized time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.timeFinalized)))\n                    self.assertEqual(sms.timeFinalized, deliverTime, 'SMS finalized time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(deliverTime, sms.timeFinalized))\n                    self.assertEqual(sms.deliveryStatus, deliveryStatus, 'SMS delivery status incorrect. Expected: \"{0}\", got: \"{1}\"'.format(deliveryStatus, sms.deliveryStatus))                \n                    self.assertEqual(sms.smsc, None, 'Text-mode SMS should not have any SMSC information')\n                finally:\n                    callbackDone[0] = True\n            self.initModem(smsStatusReportCallback=smsStatusReportCallbackFuncText)\n            self.modem.smsTextMode = False\n            def writeCallbackFunc(data):\n                def writeCallbackFunc2(data):                    \n                    self.assertEqual('AT+CMGR={0}\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGR={0}'.format(index), data))\n                    self.modem.serial.responseSequence = responseSeq\n                    def writeCallbackFunc3(data):\n                        self.assertEqual('AT+CMGD={0},0\\r'.format(index), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CMGD={0}'.format(index), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc3\n                if self.modem._smsMemReadDelete != mem:\n                    self.assertEqual('AT+CPMS=\"{0}\"\\r'.format(mem), data, 'Invalid data written to modem; expected \"{0}\", got: \"{1}\"'.format('AT+CPMS=\"{0}\"'.format(mem), data))\n                    self.modem.serial.writeCallbackFunc = writeCallbackFunc2\n                else:\n                    # Modem does not need to change read memory\n                    writeCallbackFunc2(data)\n            self.modem.serial.writeCallbackFunc = writeCallbackFunc\n            # Fake a \"new status report\" notification\n            self.modem.serial.responseSequence = ['+CDSI: \"{0}\",{1}\\r\\n'.format(mem, index)]\n            # Wait for the handler function to finish\n            while callbackDone[0] == False:\n                time.sleep(0.1)\n        self.modem.close()\n\n    def test_receiveSmsPduMode_invalidPDUsRecordedFromModems(self):\n        \"\"\" Test receiving PDU-mode SMS using data captured from failed operations/bug reports \"\"\"\n        tests = ((['+CMGR: 1,,26\\r\\n', '0006230E9126983575169498610103409544C26101034095448200\\r\\n', 'OK\\r\\n'], # see: babca/python-gsmmodem#15\n                  Sms.STATUS_RECEIVED_READ, # message read status\n                  '+62895357614989', # number\n                  35, # reference\n                  datetime(2016, 10, 30, 4, 59, 44, tzinfo=SimpleOffsetTzInfo(8)), # sentTime\n                  datetime(2016, 10, 30, 4, 59, 44, tzinfo=SimpleOffsetTzInfo(7)), # deliverTime\n                  StatusReport.DELIVERED), # delivery status\n                 )\n\n        callbackDone = [False]\n\n        for modemResponse, msgStatus, number, reference, sentTime, deliverTime, deliveryStatus in tests:\n            def smsCallbackFunc1(sms):\n                try:\n                    self.assertIsInstance(sms, gsmmodem.modem.StatusReport)\n                    self.assertEqual(sms.status, msgStatus, 'Status report read status incorrect. Expected: \"{0}\", got: \"{1}\"'.format(msgStatus, sms.status))\n                    self.assertEqual(sms.number, number, 'SMS sender number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(number, sms.number))\n                    self.assertEqual(sms.reference, reference, 'Status report SMS reference number incorrect. Expected: \"{0}\", got: \"{1}\"'.format(reference, sms.reference))\n                    self.assertIsInstance(sms.timeSent, datetime, 'SMS sent time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.timeSent)))\n                    self.assertEqual(sms.timeSent, sentTime, 'SMS sent time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(sentTime, sms.timeSent))\n                    self.assertIsInstance(sms.timeFinalized, datetime, 'SMS finalized time type invalid. Expected: datetime.datetime, got: {0}\"'.format(type(sms.timeFinalized)))\n                    self.assertEqual(sms.timeFinalized, deliverTime, 'SMS finalized time incorrect. Expected: \"{0}\", got: \"{1}\"'.format(deliverTime, sms.timeFinalized))\n                    self.assertEqual(sms.deliveryStatus, deliveryStatus, 'SMS delivery status incorrect. Expected: \"{0}\", got: \"{1}\"'.format(deliveryStatus, sms.deliveryStatus))\n                    self.assertEqual(sms.smsc, None, 'This SMS should not have any SMSC information')\n                finally:\n                    callbackDone[0] = True\n\n            def writeCallback1(data):\n                if data.startswith('AT+CMGR'):\n                    self.modem.serial.flushResponseSequence = True\n                    self.modem.serial.responseSequence = modemResponse\n\n            self.initModem(smsStatusReportCallback=smsCallbackFunc1)\n            # Fake a \"new message\" notification\n            self.modem.serial.writeCallbackFunc = writeCallback1\n            self.modem.serial.flushResponseSequence = True\n            self.modem.serial.responseSequence = ['+CDSI: \"SM\",1\\r\\n']\n            # Wait for the handler function to finish\n            while callbackDone[0] == False:\n                time.sleep(0.1)\n\n\n\n\nif __name__ == \"__main__\":\n    logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    unittest.main()\n"
  },
  {
    "path": "test/test_pdu.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\" Test suite for SMS PDU encoding/decoding algorithms \"\"\"\n\nfrom __future__ import unicode_literals\n\nimport sys, unittest, random, codecs\nfrom datetime import datetime, timedelta\n\nfrom . import compat # For Python 2.6, 3.0-2 compatibility\n \nimport gsmmodem.pdu\nfrom gsmmodem.util import SimpleOffsetTzInfo\n\nclass TestSemiOctets(unittest.TestCase):\n    \"\"\" Tests the semi-octet encoder/decoder \"\"\"\n    \n    def setUp(self):\n        self.tests = (('15125551234', bytearray([0x51, 0x21, 0x55, 0x15, 0x32, 0xf4])),\n                      ('123', bytearray([0x21, 0xf3])),\n                      ('1234', bytearray([0x21, 0x43]))) \n    \n    def test_encode(self):\n        \"\"\" Tests the semi-octet encoding algorithm \"\"\"        \n        for plaintext, encoded in self.tests:\n            result = gsmmodem.pdu.encodeSemiOctets(plaintext)\n            self.assertEqual(result, encoded, 'Failed to encode plaintext string: \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(plaintext, [b for b in encoded], [b for b in result]))\n    \n    def test_decode(self):\n        \"\"\" Tests the semi-octet decoding algorithm \"\"\"        \n        for plaintext, encoded in self.tests:\n            # Test different parameter types: bytearray, str\n            for param in (encoded, codecs.encode(compat.str(encoded), 'hex_codec')):\n                result = gsmmodem.pdu.decodeSemiOctets(param)\n                self.assertEqual(result, plaintext, 'Failed to decode data. Expected: \"{0}\", got: \"{1}\"'.format(plaintext, result))\n        \n    def test_decodeIter(self):\n        \"\"\" Tests semi-octet decoding when using a bytearray iterator and number of octets as input argument \"\"\"\n        iterTests = (('0123456789', 9, iter(bytearray(codecs.decode(b'1032547698', 'hex_codec')))),)\n        for plaintext, numberOfOctets, byteIter in iterTests:\n            result = gsmmodem.pdu.decodeSemiOctets(byteIter, numberOfOctets)\n            self.assertEqual(result, plaintext, 'Failed to decode data iter. Expected: \"{0}\", got: \"{1}\"'.format(plaintext, result))\n\n\nclass TestGsm7(unittest.TestCase):\n    \"\"\" Tests the GSM-7 encoding/decoding algorithms \"\"\"\n    \n    def setUp(self):\n        self.tests = (('', bytearray(b''), bytearray([])),\n                      ('123', bytearray(b'123'), bytearray([49, 217, 12])),\n                      ('12345678', bytearray(b'12345678'), bytearray([49, 217, 140, 86, 179, 221, 112])),\n                      ('123456789', bytearray(b'123456789'), bytearray([49, 217, 140, 86, 179, 221, 112, 57])),\n                      ('Hello World!', bytearray([0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21]), bytearray([200, 50, 155, 253, 6, 93, 223, 114, 54, 57, 4])),\n                      ('[{abc}]~', bytearray([0x1B, 0x3C, 0x1B, 0x28, 0x61, 0x62, 0x63, 0x1B, 0x29, 0x1B, 0x3E, 0x1B, 0x3D]), bytearray([27, 222, 6, 21, 22, 143, 55, 169, 141, 111, 211, 3])),\n                      ('123456789012345678901234567890', bytearray([49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48]), \n                       bytearray([49, 217, 140, 86, 179, 221, 112, 57, 88, 76, 54, 163, 213, 108, 55, 92, 14, 22, 147, 205, 104, 53, 219, 13, 151, 131, 1])),\n                      ('{åΦΓΛΩΠΨΣΘ€}', bytearray([27, 40, 15, 18, 19, 20, 21, 22, 23, 24, 25, 27, 101, 27, 41]), bytearray([27, 212, 67, 50, 161, 84, 44, 23, 76, 102, 83, 222, 164, 0])),\n                      ('a[]{}€', bytearray([97, 27, 60, 27, 62, 27, 40, 27, 41, 27, 101]), bytearray([225, 13, 111, 227, 219, 160, 54, 169, 77, 25])),\n                      )\n    \n    def test_encode(self):\n        \"\"\" Tests GSM-7 encoding algorithm \"\"\"\n        for plaintext, encoded, septets in self.tests:\n            result = gsmmodem.pdu.encodeGsm7(plaintext)\n            self.assertEqual(result, encoded, 'Failed to GSM-7 encode plaintext string: \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(plaintext, [b for b in encoded], [b for b in result]))\n\n    def test_decode(self):\n        \"\"\" Tests GSM-7 decoding algorithm \"\"\"\n        for plaintext, encoded, septets in self.tests:\n            # Test different parameter types: bytearray, str\n            for param in (encoded, compat.bytearrayToStr(encoded)):\n                result = gsmmodem.pdu.decodeGsm7(param)\n                self.assertEqual(result, plaintext, 'Failed to decode GSM-7 string: \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format([b for b in encoded], plaintext, result))\n            \n    def test_packSeptets(self):\n        \"\"\" Tests the septet-packing alogrithm for GSM-7-encoded strings \"\"\"\n        for plaintext, encoded, septets in self.tests:\n            # Test different parameter types: bytearray, str, iter(bytearray)\n            i = 0\n            for param in (encoded, compat.bytearrayToStr(encoded), iter(encoded)):\n                result = gsmmodem.pdu.packSeptets(param)\n                self.assertEqual(result, septets, 'Failed to pack GSM-7 octets into septets for string: \"{0}\" using parameter type: {1}. Expected: \"{2}\", got: \"{3}\"'.format(plaintext, type(param), [b for b in septets], [b for b in result]))\n                i+=1\n    \n    def test_unpackSeptets_no_limits(self):\n        \"\"\" Tests the septet-unpacking alogrithm for GSM-7-encoded strings (no maximum number of septets specified) \"\"\"\n        for plaintext, encoded, septets in self.tests:\n            # Test different parameter types: bytearray, str, iter(bytearray)\n            for param in (septets, compat.bytearrayToStr(septets), iter(septets)):\n                result = gsmmodem.pdu.unpackSeptets(param)\n                self.assertEqual(result, encoded, 'Failed to unpack GSM-7 septets into octets for string: \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(plaintext, [b for b in encoded], [b for b in result]))\n    \n    def test_unpackSeptets_with_limits(self):\n        \"\"\" Tests the septet-unpacking alogrithm for GSM-7-encoded strings (max number of septets specified) \"\"\"        \n        for plaintext, encoded, septets in self.tests:\n            limit = len(septets)\n            septets.extend([random.randint(0,255), random.randint(0,255), random.randint(0,255), random.randint(0,255)]) # add some garbage data (should be ignored due to numberOfSeptets being set)\n            result = gsmmodem.pdu.unpackSeptets(septets, limit)\n            self.assertEqual(result, encoded, 'Failed to unpack GSM-7 septets into {0} octets for string: \"{1}\". Expected: \"{2}\", got: \"{3}\"'.format(len(encoded), plaintext, [b for b in encoded], [b for b in result]))\n\n    def test_encodeInvalid(self):\n        \"\"\" Test encoding a string that cannot be encoded with GSM-7 \"\"\"\n        tests = ('世界您好！',)\n        for invalidStr in tests:\n            self.assertRaises(ValueError, gsmmodem.pdu.encodeGsm7, invalidStr, discardInvalid=False)\n\n    def test_encodeInvalidDiscard(self):\n        \"\"\" Tests encoding a string containing invalid GSM-7 characters when set to discard them \"\"\"\n        tests = (('a世界b您c好！', bytearray([97, 98, 99])),)\n        for invalidStr, encoded in tests:\n            result = gsmmodem.pdu.encodeGsm7(invalidStr, discardInvalid=True)\n            self.assertEqual(result, encoded, 'Failed to GSM-7 encode invalid plaintext string: \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(invalidStr, [b for b in encoded], [b for b in result]))\n\n\nclass TestUcs2(unittest.TestCase):\n    \"\"\" Tests the UCS2 encoding/decoding algorithms \"\"\"\n\n    def setUp(self):\n        self.tests = (('あ叶葉', bytearray([0x30, 0x42, 0x53, 0xF6, 0x84, 0x49])),\n                         ('はい', bytearray([0x30, 0x6F, 0x30, 0x44])))\n    \n    def test_encode(self):\n        \"\"\" Tests GSM-7 encoding algorithm \"\"\"\n        for plaintext, encoded in self.tests:\n            result = gsmmodem.pdu.encodeUcs2(plaintext)\n            self.assertEqual(result, encoded, 'Failed to UCS-2 encode plaintext string: \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(plaintext, [b for b in encoded], [b for b in result]))\n\n    def test_decode(self):\n        \"\"\" Tests GSM-7 decoding algorithm \"\"\"\n        for plaintext, encoded in self.tests:\n            result = gsmmodem.pdu.decodeUcs2(iter(encoded), len(encoded))\n            self.assertEqual(result, plaintext, 'Failed to decode UCS-2 string: \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format([b for b in encoded], plaintext, result))\n            \n\nclass TestSmsPduAddressFields(unittest.TestCase):\n    \"\"\" Tests for SMS PDU address fields (these methods are not meant to be public) \"\"\"\n    \n    def setUp(self):\n        self.tests = (('+9876543210', 7, b'0A918967452301', b'0A918967452301'),\n                 ('+9876543210', 7, b'0A918967452301000000', b'0A918967452301'), # same as above, but checking read limits\n                 ('+987654321', 7, b'099189674523F1000000', b'099189674523F1'), \n                 ('+27829135934', 8, b'0B917228195339F4', b'0B917228195339F4'),\n                 ('abc', 5, b'06D061F118', b'06D061F118'),\n                 ('abc', 5, b'06D061F118D3F1FF0032', b'06D061F118'), # same as above, but checking read limits                 \n                 ('FRANCOIS', 9, b'0ED04669D0397C26A7', b'0ED04669D0397C26A7'),\n                 ('a[]{}€', 12, b'14D0E10D6FE3DBA036A94D19', b'14D0E10D6FE3DBA036A94D19'),\n                 ('0129998765', 7, b'0AA11092997856', b'0AA11092997856') # local number\n                 )\n    \n    def test_decodeAddressField(self):        \n        for plaintext, bytesRead, hexEncoded, realHexEncoded in self.tests:\n            byteIter = iter(bytearray(codecs.decode(hexEncoded, 'hex_codec')))\n            resultValue, resultNumBytesRead = gsmmodem.pdu._decodeAddressField(byteIter, log=True)\n            self.assertEqual(resultValue, plaintext, 'Failed to decode address field data \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(hexEncoded, plaintext, resultValue))\n            self.assertEqual(resultNumBytesRead, bytesRead, 'Incorrect \"number of bytes read\" returned for data \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(hexEncoded, bytesRead, resultNumBytesRead))\n    \n    def test_encodeAddressField(self):\n        for plaintext, bytesRead, hexEncoded, realHexEncoded in self.tests:\n            expected = bytearray(codecs.decode(realHexEncoded, 'hex_codec'))\n            result = gsmmodem.pdu._encodeAddressField(plaintext)\n            self.assertEqual(result, expected, 'Failed to encode address field data \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(plaintext, realHexEncoded, codecs.encode(compat.str(result), 'hex_codec').upper()))\n\nclass TestSmsPduSmscFields(unittest.TestCase):\n    \"\"\" Tests for SMS PDU SMSC-specific address fields (these methods are not meant to be public)\n\n    Note: SMSC fields are encoded *slightly* differently from \"normal\" address fields (the length indicator is different)\n    \"\"\"\n    \n    def setUp(self):\n        self.tests = (('+9876543210', 7, b'06918967452301', b'06918967452301'),\n                 ('+9876543210', 7, b'06918967452301000000', b'06918967452301'), # same as above, but checking read limits\n                 ('+987654321', 7, b'069189674523F1000000', b'069189674523F1'), \n                 ('+2782913593', 7, b'06917228195339', b'06917228195339'))\n        \n    def test_decodeSmscField(self):        \n        for plaintext, bytesRead, hexEncoded, realHexEncoded in self.tests:\n            byteIter = iter(bytearray(codecs.decode(hexEncoded, 'hex_codec')))\n            resultValue, resultNumBytesRead = gsmmodem.pdu._decodeAddressField(byteIter, smscField=True)\n            self.assertEqual(resultValue, plaintext, 'Failed to decode SMSC address field data \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(hexEncoded, plaintext, resultValue))\n            self.assertEqual(resultNumBytesRead, bytesRead, 'Incorrect \"number of bytes read\" returned for data \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(hexEncoded, bytesRead, resultNumBytesRead))\n    \n    def test_encodeSmscField(self):\n        for plaintext, bytesRead, hexEncoded, realHexEncoded in self.tests:\n            expected = bytearray(codecs.decode(realHexEncoded, 'hex_codec'))\n            result = gsmmodem.pdu._encodeAddressField(plaintext, smscField=True)\n            self.assertEqual(result, expected, 'Failed to encode SMSC address field data \"{0}\". Expected: \"{1}\", got: \"{2}\"'.format(plaintext, realHexEncoded, codecs.encode(compat.str(result), 'hex_codec').upper()))\n\n\nclass TestRelativeValidityPeriod(unittest.TestCase):\n    \"\"\" Tests for SMS PDU relative validity period encoding/decoding (these methods are not meant to be public) \"\"\"\n    \n    def setUp(self):\n        self.tests = ((timedelta(minutes=30), 5),\n                      (timedelta(hours=16), 151),\n                      (timedelta(days=3), 169),\n                      (timedelta(weeks=5), 197))\n        \n    def test_encode(self):\n        for validity, tpVp in self.tests:\n            result = gsmmodem.pdu._encodeRelativeValidityPeriod(validity)\n            self.assertEqual(result, tpVp, 'Failed to encode relative validity period: {0}. Expected: \"{1}\", got: \"{2}\"'.format(validity, tpVp, result))\n            self.assertIsInstance(result, tpVp.__class__, 'Invalid data type returned; expected {0}, got {1}'.format(tpVp.__class__, result.__class__))\n    \n    def test_decode(self):\n        for validity, tpVp in self.tests:\n            result = gsmmodem.pdu._decodeRelativeValidityPeriod(tpVp)\n            self.assertEqual(result, validity, 'Failed to decode relative validity period: {0}. Expected: \"{1}\", got: \"{2}\"'.format(tpVp, validity, result))\n    \n    def test_decode_invalidTpVp(self):\n        tpVp = 2048 # invalid since > 255\n        self.assertRaises(ValueError, gsmmodem.pdu._decodeRelativeValidityPeriod, tpVp)\n    \n    def test_encode_validityPeriodTooLong(self):\n        validity = timedelta(weeks=1000)\n        self.assertRaises(ValueError, gsmmodem.pdu._encodeRelativeValidityPeriod, validity)\n\n\nclass TestTimestamp(unittest.TestCase):\n    \"\"\" Tests for SMS PDU timestamp encoding used for absolute validity period encoding/decoding (these methods are not meant to be public) \"\"\"\n    \n    def setUp(self):\n        self.tests = ((datetime(2015, 11, 27, 0, 0, 0, tzinfo=SimpleOffsetTzInfo(0)), b'51117200000000'),\n                      (datetime(2015, 11, 27, 0, 0, 0, tzinfo=SimpleOffsetTzInfo(2)), b'51117200000080'), # same as previous but with GMT+2 timezone\n                      (datetime(2007, 4, 12, 23, 25, 42, tzinfo=SimpleOffsetTzInfo(8)), b'70402132522423'),\n                      (datetime(2007, 4, 12, 23, 25, 42, tzinfo=SimpleOffsetTzInfo(-8)), b'7040213252242B'), # same as previous but with GMT-8 timezone\n                      )\n    \n    def test_encode(self):\n        for timestamp, encodedHex in self.tests:\n            encoded = bytearray(codecs.decode(encodedHex, 'hex_codec'))\n            result = gsmmodem.pdu._encodeTimestamp(timestamp)\n            self.assertEqual(result, encoded, 'Failed to encode timestamp: {0}. Expected: \"{1}\", got: \"{2}\"'.format(timestamp, encodedHex, codecs.encode(compat.str(result), 'hex_codec').upper()))\n    \n    def test_decode(self):\n        for timestamp, encoded in self.tests:\n            result = gsmmodem.pdu._decodeTimestamp(encoded)\n            self.assertEqual(result, timestamp, 'Failed to decode timestamp: {0}. Expected: \"{1}\", got: \"{2}\"'.format(encoded, timestamp, result))\n            \n    def test_encode_noTimezone(self):\n        \"\"\" Tests encoding without timezone information \"\"\"\n        timestamp = datetime(2013, 3, 1, 12, 30, 21)\n        self.assertRaises(ValueError, gsmmodem.pdu._encodeTimestamp, timestamp)\n\n\nclass TestSmsPduTzInfo(unittest.TestCase):\n    \"\"\" Basic tests for the SmsPduTzInfo class \"\"\"\n    \n    def test_pickle(self):\n        \"\"\" Ensure SmsPduTzInfo objects can be pickled (mentioneded as requirement of tzinfo implementations in Python docs) \"\"\"\n        import pickle\n        obj = gsmmodem.pdu.SmsPduTzInfo('08')\n        self.assertIsInstance(obj, gsmmodem.pdu.SmsPduTzInfo)\n        pickledObj = pickle.dumps(obj)\n        self.assertNotEqual(obj, pickledObj)\n        unpickledObj = pickle.loads(pickledObj)\n        self.assertIsInstance(unpickledObj, gsmmodem.pdu.SmsPduTzInfo)\n        self.assertEqual(obj.utcoffset(0), unpickledObj.utcoffset(0))\n    \n    def test_dst(self):\n        \"\"\" Test SmsPduTzInfo.dst() \"\"\"\n        obj = gsmmodem.pdu.SmsPduTzInfo('08')\n        self.assertEqual(obj.dst(0), timedelta(0))\n        \n    def test_utcoffset(self):\n        \"\"\" Test SmsPduTzInfo.utcoffest() \"\"\"\n        tests = (('08', 2), ('B2', -8))\n        for pduOffsetStr, offset in tests:\n            result = gsmmodem.pdu.SmsPduTzInfo(pduOffsetStr)\n            expected = SimpleOffsetTzInfo(offset)\n            self.assertEqual(result.utcoffset(0), expected.utcoffset(0))\n\n\nclass TestUdhConcatenation(unittest.TestCase):\n    \"\"\" Tests for UDH concatenation information element \"\"\"\n    \n    def setUp(self):\n        self.tests = ((23, 1, 3, b'0003170301'), # 8-bit reference\n                      (384, 2, 4, b'080401800402') # 16-bit reference\n                      )\n        \n    def test_encode(self):\n        for ref, number, parts, ieHex in self.tests:\n            concatIe = gsmmodem.pdu.Concatenation()\n            concatIe.reference = ref\n            concatIe.number = number\n            concatIe.parts = parts\n            expected = bytearray(codecs.decode(ieHex, 'hex_codec'))\n            result = concatIe.encode()\n            self.assertEqual(result, expected, 'Failed to encode Concatenation Information Element; expected: \"{0}\", got: \"{1}\"'.format(ieHex, codecs.encode(compat.str(result), 'hex_codec').upper()))\n            # Now modify some values and ensure encoded values changes\n            concatIe.reference = ref+1\n            result = concatIe.encode()\n            self.assertNotEqual(result, expected, 'Modifications to UDH information element object not reflected in encode()')\n    \n    def test_decode(self):\n        for ref, number, parts, ieHex in self.tests:\n            ieData = bytearray(codecs.decode(ieHex, 'hex_codec'))\n            # Test IE constructor with args\n            result = gsmmodem.pdu.InformationElement(ieData[0], ieData[1], ieData[2:])\n            self.assertIsInstance(result, gsmmodem.pdu.Concatenation, 'Invalid object type returned; expected Concatenation, got {0}'.format(type(result)))\n            self.assertEqual(result.reference, ref, 'Invalid reference; expected {0}, got {1}'.format(ref, result.reference))\n            self.assertEqual(result.number, number, 'Invalid part number; expected {0}, got {1}'.format(number, result.number))\n            self.assertEqual(result.parts, parts, 'Invalid total number of parts; expected {0}, got {1}'.format(parts, result.parts))\n            # Test IE constructor with kwargs\n            result = gsmmodem.pdu.InformationElement(iei=ieData[0], ieLen=ieData[1], ieData=ieData[2:])\n            self.assertIsInstance(result, gsmmodem.pdu.Concatenation, 'Invalid object type returned; expected Concatenation, got {0}'.format(type(result)))\n            self.assertEqual(result.reference, ref, 'Invalid reference; expected {0}, got {1}'.format(ref, result.reference))\n            self.assertEqual(result.number, number, 'Invalid part number; expected {0}, got {1}'.format(number, result.number))\n            self.assertEqual(result.parts, parts, 'Invalid total number of parts; expected {0}, got {1}'.format(parts, result.parts))\n\n\nclass TestUdhPortAddress(unittest.TestCase):\n    \"\"\" Tests for UDH application port addressing scheme information element \"\"\"\n    \n    def setUp(self):\n        self.tests = ((100, 50, b'04026432'), # 8-bit addresses\n                      (1234, 5222, b'050404D21466') # 16-bit addresses\n                      )\n        \n    def test_encode(self):\n        for destination, source, ieHex in self.tests:\n            portIe = gsmmodem.pdu.PortAddress()\n            portIe.source = source\n            portIe.destination = destination\n            expected = bytearray(codecs.decode(ieHex, 'hex_codec'))\n            result = portIe.encode()\n            self.assertEqual(result, expected, 'Failed to encode PortAddress Information Element; expected: \"{0}\", got: \"{1}\"'.format(ieHex, codecs.encode(compat.str(result), 'hex_codec').upper()))\n            # Now modify some values and ensure encoded values changes\n            portIe.destination = destination+1\n            result = portIe.encode()\n            self.assertNotEqual(result, expected, 'Modifications to UDH information element object not reflected in encode()')\n    \n    def test_decode(self):\n        for destination, source, ieHex in self.tests:\n            ieData = bytearray(codecs.decode(ieHex, 'hex_codec'))\n            # Test IE constructor with args\n            result = gsmmodem.pdu.InformationElement(ieData[0], ieData[1], ieData[2:])\n            self.assertIsInstance(result, gsmmodem.pdu.PortAddress, 'Invalid object type returned; expected Concatenation, got {0}'.format(type(result)))\n            self.assertEqual(result.source, source, 'Invalid origin port number; expected {0}, got {1}'.format(source, result.source))\n            self.assertEqual(result.destination, destination, 'Invalid destination port number; expected {0}, got {1}'.format(destination, result.destination))\n            # Test IE constructor with kwargs\n            result = gsmmodem.pdu.InformationElement(iei=ieData[0], ieLen=ieData[1], ieData=ieData[2:])\n            self.assertIsInstance(result, gsmmodem.pdu.PortAddress, 'Invalid object type returned; expected Concatenation, got {0}'.format(type(result)))\n            self.assertEqual(result.source, source, 'Invalid origin port number; expected {0}, got {1}'.format(source, result.source))\n            self.assertEqual(result.destination, destination, 'Invalid destination port number; expected {0}, got {1}'.format(destination, result.destination))\n\nclass TestSmsPdu(unittest.TestCase):\n    \"\"\" Tests encoding/decoding of SMS PDUs \"\"\"\n\n    def test_encodeSmsSubmit(self):\n        \"\"\" Tests SMS PDU encoding \"\"\"\n        tests = (('+27820001111', 'Hello World!', 0, None, None, False, False, b'0001000B917228001011F100000CC8329BFD065DDF72363904'),\n                 ('+27820001111', 'Flash SMS', 0, None, None, False, True, b'0005000B917228001011F10000094676788E064D9B53'),\n                 ('+123456789', '世界您好！', 0, timedelta(weeks=52), '+44000000000', False, False, b'07914400000000F01100099121436587F90008F40A4E16754C60A8597DFF01'),\n                 ('0126541234', 'Test message: local numbers', 13, timedelta(days=3), '12345', True, False, b'04A12143F5310D0AA110624521430000A91BD4F29C0E6A97E7F3F0B9AC03B1DFE3301BE4AEB7C565F91C'),\n                 ('+27820001111', 'Timestamp validity test', 0, datetime(2013, 7, 10, 13, 39, tzinfo=SimpleOffsetTzInfo(2)), None, False, False, b'0019000B917228001011F100003170013193008017D474BB3CA787DB70903DCC4E93D3F43C885E9ED301'),\n                 )\n        for number, text, reference, validity, smsc, rejectDuplicates, sendFlash, pduHex in tests:\n            pdu = bytearray(codecs.decode(pduHex, 'hex_codec'))\n            result = gsmmodem.pdu.encodeSmsSubmitPdu(number, text, reference, validity, smsc, rejectDuplicates, sendFlash)\n            self.assertIsInstance(result, list)\n            self.assertEqual(len(result), 1, 'Only 1 PDU should have been created, but got {0}'.format(len(result)))\n            self.assertIsInstance(result[0], gsmmodem.pdu.Pdu)\n            self.assertEqual(result[0].data, pdu, 'Failed to encode SMS PDU for number: \"{0}\" and text \"{1}\". Expected: \"{2}\", got: \"{3}\"'.format(number, text, pduHex, codecs.encode(compat.str(result[0].data), 'hex_codec').upper()))\n\n    def test_decode(self):\n        \"\"\" Tests SMS PDU decoding \"\"\"\n        tests = ((b'06917228195339040B917228214365F700003130805120618005D4F29C2E03', {'type': 'SMS-DELIVER',\n                                                                                     'smsc': '+2782913593',\n                                                                                     'number': '+27821234567',\n                                                                                     'protocol_id': 0,                                                            \n                                                                                     'time': datetime(2013, 3, 8, 15, 2, 16, tzinfo=SimpleOffsetTzInfo(2)),\n                                                                                     'text': 'Test2'}),\n                 (b'07915892000000F0040B915892214365F700007040213252242331493A283D0795C3F33C88FE06C9CB6132885EC6D341EDF27C1E3E97E7207B3A0C0A5241E377BB1D7693E72E',\n                  {'type': 'SMS-DELIVER',\n                   'smsc': '+85290000000',\n                   'number': '+85291234567',\n                   'time': datetime(2007, 4, 12, 23, 25, 42, tzinfo=SimpleOffsetTzInfo(8)),\n                   'text': 'It is easy to read text messages via AT commands.'}),\n                 (b'06917228195339040B917228214365F70000313062315352800A800D8A5E98D337A910', \n                  {'type': 'SMS-DELIVER',\n                   'number': '+27821234567',\n                   'text': '@{tést}!'}),\n                 (b'07911326040000F0310D0B911326880736F40000A90FF7FBDD454E87CDE1B0DB357EB701',\n                  {'type': 'SMS-SUBMIT',\n                   'smsc': '+31624000000',\n                   'number': '+31628870634',\n                   'validity': timedelta(days=3),\n                   'text': 'www.diafaan.com'}),\n                 (b'0006D60B911326880736F4111011719551401110117195714000',\n                  {'type': 'SMS-STATUS-REPORT',\n                   'number': '+31628870634',\n                   'reference': 214}),\n                 (b'0591721891F1400781721881F800003160526104848059050003C30101916536FB1DCABEEB2074D85E064941B19CAB060319A5C522289C96D3D3ED32286C0FA7D96131BBEC024941B19CAB0603DDD36C36A88C87A7E565D0DB0D82C55EB0DB4B068BCD5C20',\n                  {'type': 'SMS-DELIVER',\n                   'number': '2781188',\n                   'smsc': '+2781191',\n                   'text': 'Hello!You have R 19.50 FREE airtime available. R 19.50 will expire on 01/07/2013. ',\n                   'udh': [gsmmodem.pdu.Concatenation(0x00, 0x03, [0xC3, 0x01, 0x01])]}),\n                 (b'07914346466554F601000B914316565811F9000806304253F68449', # Valid UCS-2 PDU\n                  {'type': 'SMS-SUBMIT',\n                   'number': '+34616585119',\n                   'smsc': '+34646456456',\n                   'text': 'あ叶葉'}),\n                 (b'0041010C910661345542F60008A0050003000301306F3044', # UCS-2 PDU; User data length is invalid in this PDU (too long)\n                  {'type': 'SMS-SUBMIT',\n                   'number': '+60164355246',\n                   'smsc': None,\n                   'udh': [gsmmodem.pdu.Concatenation(0x00, 0x03, [0x00, 0x03, 0x01])],\n                   'text': 'はい'}),\n                 (b'0591721891F101000B917228214365F700040C48656C6C6F20776F726C6421', # 8-bit data coding\n                  {'type': 'SMS-SUBMIT',\n                   'number': '+27821234567',\n                   'smsc': '+2781191',\n                   'text': 'Hello world!'}),\n                 (b'0019000B917228001011F100003170013193008017D474BB3CA787DB70903DCC4E93D3F43C885E9ED301', # absolute validity period\n                  {'text': 'Timestamp validity test',\n                   'validity': datetime(2013, 7, 10, 13, 39, tzinfo=SimpleOffsetTzInfo(2))}),\n                 # Semi-invalid status report PDU captured from a ZTE modem \n                 (b'0297F1061C0F910B487228297020F5317062419272803170624192138000',\n                  {'type': 'SMS-STATUS-REPORT',\n                   'number': '+b08427829207025', # <- broken number (invalid PDU data; the reference number is more than a single byte (or they added something))\n                   'reference': 28,\n                   'time': datetime(2013, 7, 26, 14, 29, 27, tzinfo=SimpleOffsetTzInfo(2)),\n                   'discharge': datetime(2013, 7, 26, 14, 29, 31, tzinfo=SimpleOffsetTzInfo(2))}),\n                 (b'07919762020033F1400DD0CDF2396C7EBB010008415072411084618C0500035602010053004D005300200063006F00640065003A00200034003800350036002C00200063006F006E006600690072006D006100740069006F006E0020006F00660020006100730073006F00630069006100740069006F006E0020006200650074007700650065006E0020006100630063006F0075006E007400200061006E00640020004D00650067',\n                  {'type': 'SMS-DELIVER',\n                   'smsc': '+79262000331',\n                   'number': 'Megafon',\n                   'text': 'SMS code: 4856, confirmation of association between account and Meg',\n                   'time': datetime(2014, 5, 27, 14, 1, 48, tzinfo=SimpleOffsetTzInfo(4))})\n                 )\n\n        for pdu, expected in tests:\n            result = gsmmodem.pdu.decodeSmsPdu(pdu)\n            self.assertIsInstance(result, dict)\n            for key, value in expected.items():\n                self.assertIn(key, result)\n                if key == 'udh':\n                    self.assertEqual(len(result[key]), len(value), 'Incorrect number of UDH information elements; expected {0}, got {1}'.format(len(result[key]), len(value)))\n                    for i in range(len(value)):\n                        got = result[key][i]\n                        expected = value[i]\n                        self.assertIsInstance(got, expected.__class__)\n                        self.assertEqual(expected.id, got.id)\n                        self.assertEqual(expected.dataLength, got.dataLength)\n                        self.assertEqual(expected.data, got.data)\n                        if isinstance(expected, gsmmodem.pdu.Concatenation):\n                            self.assertEqual(got.reference, expected.reference)\n                            self.assertEqual(got.parts, expected.parts)\n                            self.assertEqual(got.number, expected.number)\n                        elif isinstance(expected, gsmmodem.pdu.PortAddress):\n                            self.assertEqual(got.destination, expected.destination)\n                            self.assertEqual(got.source, expected.source)\n                else:\n                    self.assertEqual(result[key], value, 'Failed to decode PDU value for \"{0}\". Expected \"{1}\", got \"{2}\".'.format(key, value, result[key]))\n\n    def test_encodeSmsSubmit_concatenated(self):\n        \"\"\" Tests concatenated SMS encoding \"\"\"\n        tests = (('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quinostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum doloe eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum',\n                  '+15125551234',\n                  [b'0045000B915121551532F40000A0050003000301986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D4F8FD3EE33A8CC4ED359A079990C22BF41E5747DDE7E9341F4721BFE9683D2EE719A9C26D7DD74509D0E6287C56F791954A683C86FF65B5E06B5C36777181466A7E3F5B0AB4A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7DD',\n                   b'0045000B915121551532F40000A0050003000302DE73BABC4E0695F165F9384D0FD3D36F37A8CE6687DBE337881D16BFE5E939C89D9EA741753A28CC4EC7EB6938A88C0795C3A0F1BBDD7E93DFA0F1DB3D2FC7EB61BA8B584FCF41E13ABD0C4ACBEBF23288FC66BFE5A0B41B242FC3E56574D94D2ECBD37450DA0DB2BFD975383D4C2F83EC65769A0E2ACFE765D038CD66D7DB20F29BFD2E83CA',\n                   b'0045000B915121551532F400008C050003000303EA2073FD9C0ED341EE3A9B1D06C1C3F274985E97BB8AF871194E2FD7E5A079DA4D07BDC7E370791CA683C675789A1CA687E920F7DB0D82CBDF6972D94D6781E675371D947683C675363C0C8AD7D3A0B7D99C1EA7C32072795E96D7DD7450FBCD66A7E9A0B03BDD06A5C9A0F29C0E6287C56F79BD0D']\n                 ),)\n        for text, number, hexPdus in tests:\n            result = gsmmodem.pdu.encodeSmsSubmitPdu(number, text, reference=0, requestStatusReport=False, rejectDuplicates=True)\n            self.assertIsInstance(result, list)\n            self.assertEqual(len(result), len(hexPdus), 'Invalid number of PDUs returned; expected {0}, got {1}'.format(len(hexPdus), len(result)))\n            i = 0\n            for pdu in result:\n                self.assertIsInstance(pdu, gsmmodem.pdu.Pdu)\n                expectedPduHex = hexPdus[i]\n                expectedPdu = bytearray(codecs.decode(expectedPduHex, 'hex_codec'))\n                self.assertEqual(pdu.data, expectedPdu, 'Failed to encode concatentated SMS PDU (PDU {0}/{1}). Expected: \"{2}\", got: \"{3}\"'.format(i+1, len(result), expectedPduHex, codecs.encode(compat.str(pdu.data), 'hex_codec').upper()))\n                i += 1\n    \n    def test_encodeSmsSubmit_invalidValidityType(self):\n        \"\"\" Tests SMS PDU encoding when specifying an invalid object type for validity \"\"\"\n        self.assertRaises(TypeError, gsmmodem.pdu.encodeSmsSubmitPdu, **{'number': '123', 'text': 'abc', 'validity': 'INVALID'})\n    \n    def test_decode_invalidPduType(self):\n        \"\"\" Tests SMS PDU decoding when an invalid PDU type is specified \"\"\"\n        # PDU first octect: 0x43; thus PDU type: 0x03 (invalid)\n        pdu = '0043010C910661345542F60008A0050003000301306F3044'\n        self.assertRaises(gsmmodem.exceptions.EncodingError, gsmmodem.pdu.decodeSmsPdu, pdu)\n    \n    def test_decode_invalidData(self):\n        \"\"\" Tests SMS PDU decoding when completely invalid data is specified \"\"\"\n        pdu = 'AFSDSDF LJJFKLDJKLFJ# #$KJLKJL SF'\n        self.assertRaises(gsmmodem.exceptions.EncodingError, gsmmodem.pdu.decodeSmsPdu, pdu)\n        pdu = 'AEFDSDFSDFSDFS'\n        self.assertRaises(gsmmodem.exceptions.EncodingError, gsmmodem.pdu.decodeSmsPdu, pdu)\n\n    def test_encode_Gsm7_divideSMS(self):\n        \"\"\" Tests whether text will be devided into a correct number of chunks while using GSM-7 alphabet\"\"\"\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextGsm7(text)), 1)\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 12345-070 12345-080 12345-090 12345-100 12345-010 12345-020 12345-030 12345-040 12345-050 123\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextGsm7(text)), 1)\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 12345-070 12345-080 12345-090 12345-100 12345-010 12345-020 12345-030 12345-040 12345-050 1234\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextGsm7(text)), 2)\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 12345-070 12345-080 12345-090 12345-100 12345-010 12345-020 12345-030 12345-040 12345-050 12]\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextGsm7(text)), 2)\n        text = \"12345-010,12345-020,12345-030,12345-040,12345-050,12345-060,12345-070,12345-080,12345-090,12345-100,12345-110,12345-120,12345-130,12345-140,12345-150,[[[[[[[[\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextGsm7(text)), 2)\n\n    def test_encode_Ucs2_divideSMS(self):\n        \"\"\" Tests whether text will be devided into a correct number of chunks while using UCS-2 alphabet\"\"\"\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextUcs2(text)), 1)\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 1234567\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextUcs2(text)), 1)\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 12345678\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextUcs2(text)), 2)\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 123456[\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextUcs2(text)), 1)\n        text = \"12345-010 12345-020 12345-030 12345-040 12345-050 12345-060 1234567[\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextUcs2(text)), 2)\n        text = \"12345-010,12345-020,12345-030,12345-040,12345-050,12345-060,123456 12345-010,12345-020,12345-030,12345-040,12345-050,12345-060,1234567\"\n        self.assertEqual(len(gsmmodem.pdu.divideTextUcs2(text)), 2)\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_serial_comms.py",
    "content": "#!/usr/bin/env python\n\n\"\"\" Test suite for gsmmodem.serial_comms \"\"\"\n\nfrom __future__ import print_function\n\nimport sys, time, unittest, logging\nfrom copy import copy\n\nfrom . import compat # For Python 2.6 compatibility\n\nimport gsmmodem.serial_comms\nfrom gsmmodem.exceptions import TimeoutException\n\nclass MockSerialPackage(object):\n    \"\"\" Fake serial package for the GsmModem/SerialComms classes to import during tests \"\"\"\n    \n    class Serial():\n        \n        _REPONSE_TIME = 0.02\n        \n        \"\"\" Mock serial object for use by the GsmModem class during tests \"\"\"\n        def __init__(self, *args, **kwargs):\n            # The default value to read/\"return\" if responseSequence isn't set up, or None for nothing\n            #self.defaultResponse = 'OK\\r\\n'\n            self.responseSequence = []\n            self.flushResponseSequence = True\n            self.writeQueue = []\n            self._alive = True\n            self._readQueue = []            \n            self.writeCallbackFunc = None\n        \n        def read(self, timeout=None):\n            if len(self._readQueue) > 0:    \n                return self._readQueue.pop(0)                        \n            elif len(self.writeQueue) > 0:  \n                self._setupReadValue(self.writeQueue.pop(0))\n                if len(self._readQueue) > 0:\n                    return self._readQueue.pop(0)\n            elif self.flushResponseSequence and len(self.responseSequence) > 0:\n                self._setupReadValue(None)\n            \n            if timeout != None:\n                time.sleep(0.001)\n#                time.sleep(min(timeout, self._REPONSE_TIME))                \n#                if timeout > self._REPONSE_TIME and len(self.writeQueue) == 0:\n#                    time.sleep(timeout - self._REPONSE_TIME)\n                return ''\n            else:\n                while self._alive:\n                    if len(self.writeQueue) > 0:\n                        self._setupReadValue(self.writeQueue.pop(0))\n                        if len(self._readQueue) > 0:\n                            return self._readQueue.pop(0)                       \n#                    time.sleep(self._REPONSE_TIME)\n                    time.sleep(0.05)\n                    \n        def _setupReadValue(self, command):\n            if len(self._readQueue) == 0:\n                if len(self.responseSequence) > 0:\n                    value = self.responseSequence.pop(0)    \n                    if type(value) in (float, int):\n                        time.sleep(value)                        \n                        if len(self.responseSequence) > 0:                            \n                            self._setupReadValue(command)                    \n                    else:                        \n                        self._readQueue = list(value)\n\n        def write(self, data):            \n            if self.writeCallbackFunc != None:\n                self.writeCallbackFunc(data)\n            self.writeQueue.append(data)\n            \n        def close(self):\n            pass\n            \n        def inWaiting(self):\n            rqLen = len(self._readQueue)\n            for item in self.responseSequence:\n                if type(item) in (int, float):\n                    break\n                else:\n                    rqLen += len(item)\n            return rqLen\n            \n    \n    class SerialException(Exception):\n        \"\"\" Mock Serial Exception \"\"\"\n\nclass TestNotifications(unittest.TestCase):\n    \"\"\" Tests reading unsolicited notifications from the serial devices \"\"\"\n    \n    def setUp(self):\n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.tests = (['ABC\\r\\n'], \n                      [' blah blah blah \\r\\n', '12345\\r\\n'])\n\n    def test_callback(self):\n        \"\"\" Tests if the notification callback method is correctly called \"\"\"        \n        for test in self.tests:\n            callbackCalled = [False]\n            def callback(data):\n                callbackCalled[0] = [True]\n                self.assertIsInstance(data, list)\n                self.assertEqual(len(data), len(test))\n                for i in range(len(test)):\n                    self.assertEqual(data[i], test[i][:-2])\n        \n            serialComms = gsmmodem.serial_comms.SerialComms('-- PORT IGNORED DURING TESTS --', notifyCallbackFunc=callback)\n            serialComms.connect()\n            # Fake a notification\n            serialComms.serial.responseSequence = copy(test)\n            # Wait a bit for the event to be picked up\n            while len(serialComms.serial._readQueue) > 0 or len(serialComms.serial.responseSequence) > 0:\n                time.sleep(0.05)\n            self.assertTrue(callbackCalled[0], 'Notification callback function not called')\n            serialComms.close()\n    \n    def test_noCallback(self):\n        \"\"\" Tests notifications when no callback method was specified (nothing should happen) \"\"\"\n        for test in self.tests:\n            serialComms = gsmmodem.serial_comms.SerialComms('-- PORT IGNORED DURING TESTS --')\n            serialComms.connect()\n            # Fake a notification\n            serialComms.serial.responseSequence = copy(test)\n            # Wait a bit for the event to be picked up\n            while len(serialComms.serial._readQueue) > 0 or len(serialComms.serial.responseSequence) > 0:\n                time.sleep(0.05)            \n            serialComms.close()\n\nclass TestSerialException(unittest.TestCase):\n    \"\"\" Tests SerialException handling \"\"\"\n    \n    def setUp(self):\n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.serialComms = gsmmodem.serial_comms.SerialComms('-- PORT IGNORED DURING TESTS --')\n        self.serialComms.connect()\n    \n    def tearDown(self):\n        self.serialComms.close()        \n\n    def test_readLoopException(self):\n        \"\"\" Tests handling a SerialException from inside the read loop thread \"\"\"\n        self.assertTrue(self.serialComms.alive)\n        exceptionRaised = [False]\n        callbackCalled = [False]\n        \n        def brokenRead(*args, **kwargs):\n            exceptionRaised[0] = True\n            raise MockSerialPackage.SerialException()        \n        self.serialComms.serial.read = brokenRead\n        \n        def errorCallback(ex):\n            callbackCalled[0] = True\n            self.assertIsInstance(ex, MockSerialPackage.SerialException)\n        self.serialComms.fatalErrorCallback = errorCallback\n        \n        # Let the serial comms object attempt to read something\n        self.serialComms.serial.responseSequence = ['12345\\r\\n']\n        while not exceptionRaised[0]:\n            time.sleep(0.05)        \n        self.assertFalse(self.serialComms.alive)\n        time.sleep(0.05)\n        self.assertTrue(callbackCalled[0], 'Error callback not called on fatal error')\n\n\nclass TestWrite(unittest.TestCase):\n    \"\"\" Tests writing to the serial device \"\"\"\n     \n    def setUp(self):\n        self.mockSerial = MockSerialPackage()\n        gsmmodem.serial_comms.serial = self.mockSerial\n        self.serialComms = gsmmodem.serial_comms.SerialComms('-- PORT IGNORED DURING TESTS --')\n        self.serialComms.connect()\n    \n    def tearDown(self):\n        self.serialComms.close()\n        \n    def test_write(self):\n        \"\"\" Tests basic writing operations \"\"\"\n        tests = ((['OK\\r\\n'], ['OK']),\n                 (['ERROR\\r\\n'], ['ERROR']),\n                 (['first line\\r\\n', 'second line\\r\\n', 'OK\\r\\n'], ['first line', 'second line', 'OK']),\n                 # Some Huawei modems issue this response instead of ERROR for unknown commands; ensure we detect it correctly\n                 (['COMMAND NOT SUPPORT\\r\\n'], ['COMMAND NOT SUPPORT']))\n        for actual, expected in tests:\n            self.serialComms.serial.responseSequence = actual\n            self.serialComms.serial.flushResponseSequence = True\n            response = self.serialComms.write('test\\r')            \n            self.assertEqual(response, expected)\n            # Now write without expecting a response\n            response = self.serialComms.write('test2\\r', waitForResponse=False)\n            self.assertEqual(response, None) \n    \n    def test_writeTimeout(self):\n        \"\"\" Tests that the serial comms write timeout parameter \"\"\"\n        # Serial comms will not response (no response sequence specified)\n        self.assertRaises(TimeoutException, self.serialComms.write, 'test\\r', waitForResponse=True, timeout=0.1)\n\n    def test_writeTimeout_data(self):\n        \"\"\" Tests passing partial data along with a TimeoutException \"\"\"\n        self.serialComms.serial.responseSequence = ['abc\\r\\n', 0.5, 'def\\r\\n']\n        self.serialComms.serial.flushResponseSequence = True\n        try:\n            self.serialComms.write('test\\r', waitForResponse=True, timeout=0.1)\n        except TimeoutException as timeout:\n            # The 0.5s pause in the response should cause the write to timeout but still return the first part\n            self.assertEqual(timeout.data, ['abc'])\n        else:\n            self.fail('TimeoutException not thrown')\n    \n    def test_writeTimeout_noData(self):\n        \"\"\" Similar to test_writeTimeout(), but checks TimeoutException's data field is None \"\"\"\n        try:\n            self.serialComms.write('test\\r', waitForResponse=True, timeout=0.1)\n        except TimeoutException as timeout:\n            self.assertEqual(timeout.data, None)\n        else:\n            self.fail('TimeoutException not thrown')\n\n\nif __name__ == \"__main__\":\n    logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    unittest.main()\n"
  },
  {
    "path": "test/test_util.py",
    "content": "#!/usr/bin/env python\n\n\"\"\" Test suite for gsmmodem.util \"\"\"\n\nfrom __future__ import print_function\n\nimport sys, time, unittest, logging, re\nfrom datetime import timedelta\n\nfrom . import compat # For Python 2.6 compatibility\n\nfrom gsmmodem.util import allLinesMatchingPattern, lineMatching, lineStartingWith, lineMatchingPattern, SimpleOffsetTzInfo, removeAtPrefix\n\nclass TestUtil(unittest.TestCase):\n    \"\"\" Tests misc utilities from gsmmodem.util \"\"\"\n\n    def test_lineStartingWith(self):\n        \"\"\" Tests function: lineStartingWith \"\"\"\n        lines = ['12345', 'abc', 'defghi', 'abcdef', 'efg']\n        result = lineStartingWith('abc', lines)\n        self.assertEqual(result, 'abc')\n        result = lineStartingWith('d', lines)\n        self.assertEqual(result, 'defghi')\n        result = lineStartingWith('zzz', lines)\n        self.assertEqual(result, None)\n        \n    def test_lineMatching(self):\n        \"\"\" Tests function: lineMatching \"\"\"\n        lines = ['12345', 'abc', 'defghi', 'abcdef', 'efg']\n        result = lineMatching('^abc.*$', lines)\n        self.assertEqual(result.string, 'abc')\n        result = lineMatching('^\\d+$', lines)\n        self.assertEqual(result.string, '12345')\n        result = lineMatching('^ZZZ\\d+$', lines)\n        self.assertEqual(result, None)\n        \n    def test_lineMatchingPattern(self):\n        \"\"\" Tests function: lineMatchingPattern \"\"\"\n        lines = ['12345', 'abc', 'defghi', 'abcdef', 'efg']\n        result = lineMatchingPattern(re.compile('^abc.*$'), lines)\n        self.assertEqual(result.string, 'abc')\n        result = lineMatchingPattern(re.compile('^\\d+$'), lines)\n        self.assertEqual(result.string, '12345')\n        result = lineMatchingPattern(re.compile('^ZZZ\\d+$'), lines)\n        self.assertEqual(result, None)\n    \n    def test_allLinesMatchingPattern(self):\n        \"\"\" Tests function: lineStartingWith \"\"\"\n        lines = ['12345', 'abc', 'defghi', 'abcdef', 'efg']\n        result = allLinesMatchingPattern(re.compile('^abc.*$'), lines)\n        self.assertIsInstance(result, list)\n        self.assertEqual(len(result), 2)\n        self.assertEqual(result[0].string, 'abc')\n        self.assertEqual(result[1].string, 'abcdef')\n        result = allLinesMatchingPattern(re.compile('^defghi$'), lines)\n        self.assertIsInstance(result, list)\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0].string, 'defghi')\n        result = allLinesMatchingPattern(re.compile('^\\d+$'), lines)\n        self.assertIsInstance(result, list)\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0].string, '12345')\n        result = allLinesMatchingPattern(re.compile('^ZZZ\\d+$'), lines)\n        self.assertIsInstance(result, list)\n        self.assertEqual(result, [])\n        \n    def test_SimpleOffsetTzInfo(self):\n        \"\"\" Basic test for the SimpleOffsetTzInfo class \"\"\"\n        tests = (2, -4, 0, 3.5)\n        for hours in tests:\n            tz = SimpleOffsetTzInfo(hours)\n            self.assertEqual(tz.offsetInHours, hours)\n            self.assertEqual(tz.utcoffset(None), timedelta(hours=hours))\n            self.assertEqual(tz.dst(None), timedelta(0))\n            self.assertIsInstance(tz.__repr__(), str)\n\n    def test_removeAtPrefix(self):\n        \"\"\" Tests function: removeAtPrefix\"\"\"\n        tests = (('AT+CLAC', '+CLAC'), ('ATZ', 'Z'), ('+CLAC', '+CLAC'), ('Z', 'Z'))\n        for src, dst in tests:\n            res = removeAtPrefix(src)\n            self.assertEqual(res, dst)\n\n\nif __name__ == \"__main__\":\n    logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    unittest.main()\n"
  },
  {
    "path": "tools/at_cmd_init_modem.txt",
    "content": "# Simple script for GSMTerm to initialize the modem with a few common settings\n# Load this from within GSMTerm by typing: load /path/to/at_cmd_init_modem.txt\nATZ\nATE0\nAT+CFUN=1\nAT+CMEE=1\nAT+WIND=0\n\n# Network-related settings\nAT+COPS=3,0\n\n# Setup incoming calls\nAT+CLIP=1\nAT+CRC=1\nAT+CVHU=0\n\n# Setup SMS\nAT+CMGF=1\nAT+CSMP=49,167,0,0\nAT+CPMS=\"ME\",\"ME\",\"ME\"\nAT+CNMI=2,1,0,2\n"
  },
  {
    "path": "tools/gsmterm.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nLaunch script for GSMTerm\n\n@author: Francois Aucamp <francois.aucamp@gmail.com>\n\"\"\"\nfrom __future__ import print_function\n\nimport sys\n\nfrom gsmtermlib.terminal import GsmTerm, RawTerm\n\ndef parseArgs():\n    \"\"\" Argument parser for Python 2.7 and above \"\"\"\n    from argparse import ArgumentParser\n    parser = ArgumentParser(description='User-friendly terminal for interacting with a connected GSM modem.')\n    parser.add_argument('port', metavar='PORT', help='port to which the GSM modem is connected; a number or a device name.')\n    parser.add_argument('-b', '--baud', metavar='BAUDRATE', default=115200, help='set baud rate')\n    parser.add_argument('-r', '--raw',  action='store_true', help='switch to raw terminal mode')\n    return parser.parse_args()\n\ndef parseArgsPy26():\n    \"\"\" Argument parser for Python 2.6 \"\"\"\n    from gsmtermlib.posoptparse import PosOptionParser, Option\n    parser = PosOptionParser(description='User-friendly terminal for interacting with a connected GSM modem.')\n    parser.add_positional_argument(Option('--port', metavar='PORT', help='port to which the GSM modem is connected; a number or a device name.'))\n    parser.add_option('-b', '--baud', metavar='BAUDRATE', default=115200, help='set baud rate')\n    parser.add_option('-r', '--raw',  action='store_true', help='switch to raw terminal mode')\n    options, args = parser.parse_args()\n    if len(args) != 1:\n        parser.error('Incorrect number of arguments - please specify a PORT to connect to, e.g. {0} /dev/ttyUSB0'.format(sys.argv[0]))\n    else:\n        options.port = args[0]\n        return options\n\ndef main():\n    args = parseArgsPy26() if sys.version_info[0] == 2 and sys.version_info[1] < 7 else parseArgs()\n    if args.raw:\n        gsmTerm = RawTerm(args.port, args.baud)\n    else:\n        gsmTerm = GsmTerm(args.port, args.baud)\n\n    gsmTerm.start()\n    gsmTerm.rxThread.join()\n    print('Done.')\n\nif __name__ == '__main__':\n    main()\n\n"
  },
  {
    "path": "tools/gsmtermlib/__init__.py",
    "content": ""
  },
  {
    "path": "tools/gsmtermlib/atcommands.py",
    "content": "\nCATEGORIES = ('General', 'Call Control', 'Network Service', 'Security', 'Phonebook', 'SMS', 'Supplementary Services', 'Data', 'Fax', 'Fax Class 2', 'V24-V25', 'Specific')\n\nc = CATEGORIES\n\n# Format: (COMMAND, (CATEGORY, NAME, VALUES, DEFAULT, DESCRIPTION))\nATCOMMANDS = (\n# General\n('AT+CGMI', (c[0], 'Manufacturer Identification', None, None, 'Displays the manufacturer identification.')),\n('AT+CGMM', (c[0], 'Request Model Identification', None, None, 'Displays the supported frequency bands. With multi-band products the response may be a combination of different bands.')),\n('AT+CGMR', (c[0], 'Request Revision Identification', None, None, 'Displays the revised software version.')),\n('AT+CGSN', (c[0], 'Product Serial Number', None, None, 'Allows the user application to get the IMEI (International Mobile Equipment Identity, 15-digit number) of the product.')),\n('AT+CSCS', (c[0], 'Select TE Character Set', (('<Character Set>', \"\"\"GSM - GSM default alphabet (default value).\nPCCP437 - PC character set code page 437.\nCUSTOM - User defined character set (cf. +WCCS command).\nHEX - Hexadecimal mode. No character set used; the user can read or write hexadecimal values.\"\"\"),), None, 'Informs the ME which character set is used by the TE. The ME can convert each character of \\\nentered or displayed strings. This is used to send, read or write short messages. See also +WPCS for the phonebooks\\' character sets.')),\n('AT+WPCS', (c[0], 'Phonebook Character Set', (('<Character Set>', \"\"\"TRANSPARENT - Transparent mode. The strings are displayed and entered as they are stored in SIM or in ME.\nCUSTOM - User defined character set (cf. +WCCS command).\nHEX - Hexadecimal mode. No character set used; the user can read or write hexadecimal values.\"\"\"),), None, 'Informs the ME which character set is used by the TE for the phonebooks. The ME can convert\\\neach character of entered or displayed strings. This is used to read or write phonebook entries.\\\nSee also +CSCS for the short messages character sets.')),\n('AT+CIMI', (c[0], 'Request IMSI', None, None,  'Reads and identifies the IMSI (International Mobile Subscriber Identity) of the SIM card. The PIN may need to be entered before reading the IMSI')),\n('AT+CCID', (c[0], 'Card Identification', None, None,  'Orders the product to read the EF-CCID file on the SIM card.')),\n('AT+GCAP', (c[0], 'Capabilities List', None, None,  'Displays the complete list of capabilities.')),\n('A/', (c[0], 'Repeat Last Command', None, None,  'Repeats the previous command. Only the A/ command itself cannot be repeated.')),\n('AT+CPOF', (c[0], 'Power Off', None, None,  'Stops the GSM software stack as well as the hardware layer. The AT+CFUN=0 command is equivalent to +CPOF.')),\n('AT+CFUN', (c[0], 'Set Phone Functionality', (('<functionality level>', \"\"\"0: Set minimum functionality; IMSI detach procedure\n1: Set the full functionality mode with a complete software reset\"\"\"),), None,  \"Selects the mobile station's level of functionality. When the application wants to stop the \\\nproduct with a power off, or if the application wants to force the product to execute an IMSI \\\nDETACH procedure, then it must send: AT+CFUN=0 (equivalent to AT+CPOF). This command \\\nexecutes an IMSI DETACH and makes a backup copy of some internal parameters in SIM and \\\nin EEPROM. The SIM card cannot then be accessed. If the mobile equipment is not powered \\\noff by the application after this command has been sent, a re-start command (AT+CFUN=1) will \\\nhave to issued to restart the whole GSM registration process. If the mobile equipment is turned \\\noff after this command, then a power on will automatically restart the whole GSM process. The \\\nAT+CFUN=1 command restarts the entire GSM stack and GSM functionality: a complete \\\nsoftware reset is performed. All parameters are reset to their previous values if AT&W was not \\\nused. If you write entries in the phonebook (+CPBW) and then reset the product directly \\\n(AT+CFUN=1, with no previous AT+CFUN=0 command), some entries may not be written (the \\\nSIM task does not have enough time to write entries in the SIM card). In addition, the OK \\\nresponse will be sent at the last baud rate defined by the +IPR command. With the \\\nautobauding mode the response can be at a different baud rate, it is therefore preferable to \\\nsave the defined baud rate with AT&W before directly sending the AT+CFUN=1 command.\")),\n('AT+CPAS', (c[0], 'Phone Activity Status', None, None,  \"\"\"Returns the activity status of the mobile equipment.\nResponse is: +CPAS: <pas>\nwhere <pas> is:\n 0 ready (allow commands from TA/TE)\n 1 unavailable (does not allow commands)\n 2 unknown\n 3 ringing (ringer is active)\n 4 call in progress\n 5 asleep (low functionality)\"\"\")),\n('AT+CMEE', (c[0], 'Report Mobile Equipment Errors', (('<error reporting flag>', \"\"\"0: Disable ME error reports; use only ERROR\n1: Enable +CME ERROR: <xxx> or +CMS ERROR: <xxx>\n2: Enable error result codes with verbose (string) values\"\"\"),), None,  'Disables or enables the use of the \"+CME ERROR: <xxx>\" or \"+CMS ERROR:<xxx>\" result code instead of simply \"ERROR\".')),\n('AT+CKPD', (c[0], 'Keypad Control', (('<keys>', 'Keyboard sequence; string of the following characters (0-9, *, #)'),), None,  'Emulates the ME keypad by sending each keystroke as a character in a <keys> string.\\n\\\nIf emulation fails, a +CME ERROR: <err> is returned. If emulation succeeds, the result \\\ndepends on the GSM sequence activated.')),\n('AT+CCLK', (c[0], 'Clock Management', (('<date and time string>', 'String format for date/time is \"yy/MM/dd,hh:mm:ss\"\\nNote: Valid years are 98 (for 1998) to 97 (for 2097). The seconds field is not mandatory.'),), None,  'Sets or gets the current date and time of the ME real-time clock.')),\n('AT+CALA', (c[0], 'Alarm Management', (('<date and time string>', 'String format for alarms: \"yy/MM/dd,hh:mm:ss\" (see +CCLK)\\nNote: Seconds are taken into account.'), ('<index>', 'Offset in the alarm list, range 1 to 16')), None,  'Sets the alarm date/time in the ME. The maximum number of alarms is 16.')),\n# Call Control\n('ATD', (c[1], 'Dial command', (('<nb>', 'Destination phone number'),), None,  \"The ATD command sets a voice, data or fax call. As per GSM 02.30, the dial command also \\\ncontrols supplementary services.\\n\\\nFor a data or a fax call, the application sends the following ASCII string to the product (the bearer must \\\nbe previously selected with the +CBST command):\\n\\\n ATD<nb> where <nb> is the destination phone number;\\n\\\nFor a voice call, the application sends the following ASCII string to the product: (the bearer may be \\\nselected previously, if not a default bearer is used).\\n\\\n ATD<nb>; where <nb> is the destination phone number.\\n\\\nPlease note that for an international number, the local international prefix does not need to be set \\\n(usually 00) but does need to be replaced by the '+' character.\\n\\\n\\nThere are other varieties of the ATD command available (using phonebook memory); please search online for help on these.\")),\n('ATH', (c[1], 'Hang-Up command',  (('<n>', '0: Ask for disconnection (default value)\\n1: Ask for outgoing call disconnection'),), None,  'The ATH (or ATH0) command disconnects the remote user. In the case of multiple calls, all calls are released \\\n(active, on-hold and waiting calls). The specific ATH1 command has been appended to disconnect the current \\\noutgoing call, only in dialing or alerting state (ie. ATH1 can be used only after the ATD command, and before \\\nits terminal response (OK, NO CARRIER, ...). It can be useful in the case of multiple calls.')),\n('ATA', (c[1], 'Answer a Call', None, None, 'When the product receives a call, it sets the RingInd signal and sends the ASCII \"RING\" or \"+CRING: \\\n<type>\" string to the application (+CRING if the cellular result code +CRC is enabled). Then it waits for the \\\napplication to accept the call with the ATA command.')),\n('AT+CEER', (c[1], 'Extended Error Report', None, None,  'This command gives the cause of call release when the last call set up (originating or answering) failed.')),\n('AT+VTD', (c[1], 'DTMF Signals - set tone duration', (('<v>', 'tone duration.\\n*100 is the duration in ms. If < 4, tone duration is 300 ms; if n > 255, the value used is modulo 256.\\nDefault value: 300 ms, that is <n> = 3.'),), None,  'The modem can send DTMF tones over the GSM network. This command is used \\\nto define tone duration (the default value is 300ms). To define this duration, the application uses:\\n\\\nAT+VTD=<n> where <n>*100 gives the duration in ms. If n < 4, tone duration is 300 ms.\\n\\\nSee also: AT+VTS')),\n('AT+VTS', (c[1], 'DTMF Signals - send tone', (('<Tone>', 'DTMF tone to transmit. Tone is in {0-9, *, #, A, B, C, D}'),), None, 'The modem can send DTMF tones over the GSM network. This command enables \\\ntones to be transmitted only when there is an active call.\\nSee also: AT+VTD\\n\\n\\\nExample:\\n\\\nTo send tone sequence 13#, the application sends:\\n\\\nAT+VTS=1;+VTS=3;+VTS=#')),\n('ATDL', (c[1], 'Redial Last Telephone Number', None, None,  'This command redials the last number used in the ATD command. The last number dialed is displayed followed by \";\" for voice calls only.')),\n('AT%D', (c[1], 'Automatic Dialing with DTR', (('<n>', 'Enable or disables automatic message transmission or number dialing.\\n\\\nInforms the product that the number is a voice rather than a fax or data number.\\n\\\n0 Disables automatic DTR number dialing / message transmission.\\n\\\n1; Enables automatic DTR dialing if DTR switches from OFF to ON; Dials the phone number in the first \\\nlocation of the ADN phonebook. Voice call.\\n\\\n1 Activates automatic DTR dialing if DTR switches from OFF to ON; Dials the phone number in the first \\\nlocation of the ADN phonebook. Data or Fax call.\\n\\\n2 Activates automatic DTR message transmission if DTR switches from OFF to ON.'),), None,  'This command enables and disables:\\n\\\n- Automatic dialing of the phone number stored in the first location of the ADN phonebook,\\n\\\n- Automatic sending of the short message (SMS) stored in the first location of the SIM.\\n\\\nThe number is dialed when DTR OFF switches ON. The short message is sent when DTR OFF switches ON.')),\n('ATS0', (c[1], 'Automatic Answer', (('<value>', 'is the number of rings before automatic answer (3 characters padded with zeros)\\n\\\nRange of values is 0 to 255'),), None,  'This S0 parameter determines and controls the modem automatic answering mode.')),\n('AT+CICB', (c[1], 'Incoming Call Bearer', (('<mode>', '0: Data\\n1: Fax\\n2: Speech'),), None,  'This command sets the type of incoming calls when no incoming bearer is given (see +CSNS).\\nNote: Setting the +CICB command affects the current value of +CSNS.')),\n('AT+CSNS', (c[1], 'Single Numbering Scheme', (('<mode>', '0: Voice\\n2: Fax\\n4: Data'),), None,  'This command selects the bearer to be used when an MT single numbering scheme call is set up (see +CICB).\\nNote: Setting the +CSNS command affects the current value of +CICB.')),\n('AT+VGR', (c[1], 'Gain Control - Reception', (('<Rgain>', 'reception gain'),), None,  'This command is used by the application to tune the receive gain of the speaker.')),\n('AT+VGT', (c[1], 'Gain Control - Transmission', (('<Tgain>', 'transmission gain'),), None,  'This command is used by the application to tune the transmit gain of the microphone.')),\n('AT+CMUT', (c[1], 'Microphone Mute Control', (('<mode>', '0: microphone mute off (default)\\n1: microphone mute on'),), None, 'This command mutes the microphone input on the device. This command is only allowed during a call.')),\n('AT+CVHU', (c[1], 'Voice Hangup Control', (('<mode>', '0: \"Drop DTR\" ignored but OK response given. ATH disconnects.\\n\\\n1: \"Drop DTR\" and ATH ignored but OK response given.\\n\\\n2: \"Drop DTR\" behavior according to &D setting. ATH disconnects.'),), None, 'This command selects whether ATH or \"drop DTR\" causes a voice connection to be disconnected or not. Voice connection also includes alternating mode calls that are currently in voice mode.\\n\\\nWhen <mode>=2, this command must be viewed in conjunction with the V.25ter command &D, or &D will be ignored')),\n# Network Service\n('AT+CSQ', (c[2], 'Signal Quality', None, (('<rssi>', '0: -113 dBm or less\\n1: -111 dBm\\n2 to 30: -109 to -53 dBm\\n31: -51dBm or greater\\n99: not known or not detectable'),\n                                           ('<ber>', '0...7: as RXQUAL values in the table GSM 05.08')),\n            'This command determines the received signal strength indication (<rssi>) and the channel bit error rate (<ber>) with or without a SIM card inserted')),\n('AT+COPS', (c[2], 'Operator Selection', (('<mode>', '0: automatic (default value)\\n1: manual\\n2: deregistration; ME will be unregistered until <mode>=0 or 1 is selected.\\n3: set only <format> (for read command AT+COPS?)\\n4: manual / automatic (<oper> shall be present), if manual selection fails, automatic mode is entered.\\n<format>: format of <oper> field'),\n                                          ('<format>', '0: long alphanumeric format <oper>\\n1: short alphanumeric format <oper>\\n2: numeric <oper> (default value) <stat>: status of <oper>\\n\\n<stat>\\n 0: unknown\\n 1: available\\n 2: current\\n 3: forbidden'),\n                                          ('<oper>', 'operator identifier (MCC/MNC in numeric format only for operator selection)\\nThe long alphanumeric format can be up to 16 characters long. The short alphanumeric format can be up to 8 characters long.'),\n                                         ), None, 'Select the Network Operator.')),\n('AT+CREG', (c[2], 'Network Registration', (('<mode>', '0: Disable network registration unsolicited result code (default)\\n1: Enable network registration code result code +CREG: <stat>\\n2: Enable network registration and location information unsolicited result code +CREG: <stat>,<lac>,<ci> if there is a change of network cell.'),),\n                                           (('<stat>', '0: not registered, ME is not currently searching for a new operator.\\n\\\n1: registered, home network.\\n2: not registered, ME currently searching for a new operator to register to.\\n\\\n3: registration denied.\\n4: unknown.\\n5: registered, roaming.'),\n                                            ('<lac>', 'string type; two byte location area code in hexadecimal format'),\n                                            ('<ci>', 'string type; two byte cell ID in hexadecimal format')),\n             'This command is used by the application to ascertain the registration status of the device.')),\n('AT+WOPN', (c[2], 'Read Operator Name')),\n('AT+WOPN', (c[2], 'Selection of Preferred PLMN List')),\n('AT+CPLS', (c[2], 'Selection of Preferred PLMN List')),\n('AT+CPOL', (c[2], 'Preferred Operator List')),\n('AT+COPN', (c[2], 'Read Operator Name', None, (('<NumOper>', 'the operator in numeric format'), ('<AlphaOper>', 'the operator in long alphanumeric format')), 'This command returns the list of all operator names (in numeric and alphanumeric format) stored in the module.')),\n# Security\n('AT+CPIN', (c[3], 'Enter PIN', (('<pin>', 'the personal identification number'), ('<puk>', 'the personal unblocking key needed to change the PIN; syntax: AT+CPIN=<puk>,<new pin>')), None, 'This command enters the ME passwords (CHV1 / CHV2 / PUK1 / PUK2, etc.), that are required before any ME functionality can be used.')),\n('AT+CPIN2', (c[3], 'Enter PIN2', (('<pin2>', 'the personal identification number 2'), ('<puk2>', 'the personal unblocking key 2 needed to change the PIN 2; syntax: AT+CPIN=<puk2>,<new pin 2>')), None, 'This command validates the PIN2 code (CHV2) or the PUK2 code (UNBLOCK CHV2) and defines a new \\\nPIN2 code. Of course, the +CPIN command allows PIN2 or PUK2 codes to be validated, but only when the \\\nlast command executed resulted in PIN2 authentication failure. PIN2 length is between 4 and 8 digits; PUK2 \\\nlength is 8 digits only.')),\n('AT+CPINC', (c[3], 'PIN Remaining Attempt Number', None, (('<n1>', 'attempts left for PIN1 (0 = blocked, 3 max)'),\n                                                           ('<n2>', 'attempts left for PIN2 (0 = blocked, 3 max)'),\n                                                           ('<k1>', 'attempts left for PUK1 (0 = blocked, 10 max)'),\n                                                           ('<k2>', 'attempts left for PUK2 (0 = blocked, 10 max)')),\n              'This command gets the number of valid attempts for PIN1 (CHV1), PIN2 (CHV2), PUK1 (UNBLOCK CHV1) and PUK2 (UNBLOCK CHV2) identifiers.')),\n('AT+CLCK', (c[3], 'Call Barring')),\n('AT+CPWD', (c[3], 'Change Password')),\n# Phonebook\n('AT+CPBS', (c[4], 'Select Phonebook Memory Storage', (('\"SM\"', 'ADN (SIM phonebook)'),\n                                                       ('\"FD\"', 'FDN (SIM Fix Dialing, restricted phonebook)'),\n                                                       ('\"ON\"', 'MSISDN (SIM own numbers)'),\n                                                       ('\"EN\"', 'EN (SIM emergency number)'),\n                                                       ('\"LD\"', 'LND (combined ME and SIM last dialing phonebook)'),\n                                                       ('\"MC\"', 'MSD (ME missed calls list)'),\n                                                       ('\"ME\"', 'ME (ME phonebook)'),\n                                                       ('\"MT\"', 'MT (combined ME and SIM phonebook)'),\n                                                       ('\"RC\"', 'LIC (ME received calls list)'),\n                                                       ('\"SN\"', 'SDN (Services dialing phonebook)')),\n             None, 'This command selects phonebook memory storage.', 'Available Phonebooks:')),\n('AT+CPBR', (c[4], 'Read Phonebook Entries', (('<first_entry>', 'Location of phonebook entry or start of range of locations (if <last_entry> is specified) of the phonebook entries'),\n                                              ('<last_entry>', 'End of range of locations of the phonebook entries')), None, 'This command returns phonebook entries for a range of locations from the current phonebook memory storage selected with +CPBS.')),\n('AT+CPBF', (c[4], 'Find Phonebook Entries', (('<string>', 'Searched starting string (depends on the format of the data stored in the phonebooks'),), None, 'This command returns phonebook entries with alphanumeric fields starting with a given string. The \\\n+CPBF command can be used to display all phonebook entries sorted in alphabetical order. This command is not allowed for \"LD\", \"RC\", \"MC\", \"SN\" or \"EN\" phonebooks, which do not contain alphanumeric fields.')),\n('AT+CPBW', (c[4], 'Write Phonebook Entry', (('<index>', 'Integer type value depending on the capacity of the phonebook memory'),\n                                              ('<number>', 'Phone number in ASCII format'),\n                                              ('<type>', 'TON/NPI (Type of address byte in integer format)'),\n                                              ('text>', 'Text label/name of entry. String type')), None, 'This command writes a phonebook entry in location number <index> in the current phonebook memory storage.\\n\\\nThis command is not allowed for \"EN\", \"LD\", \"MC\", \"RC\", \"MT\", and \"SN\" phonebooks (they cannot be written).')),\n('AT+CPBP', (c[4], 'Phonebook Phone Search')),\n('AT+CPBN', (c[4], 'Move Action in Phonebook')),\n('AT+CNUM', (c[4], 'Subscriber Number', None, (('<alphax>', 'optional alphanumeric string associated with <numberx>'),\n                                               ('<numberx>', 'string type phone number with format as specified by <typex>'),\n                                               ('<typex>', 'type of address byte in integer format')), 'This command returns the subscriber MSISDN(s). If the subscriber has different MSISDNs for different services, each MSISDN is returned in a separate line.')),\n('AT+WAIP', (c[4], 'Avoid Phonebook Initialization')),\n('AT+WDCP', (c[4], 'Delete Calls Phonebook')),\n('AT+CSVM', (c[4], 'Set Voice Mail Number')),\n# Short Messages (SMS)\n('AT+CSMS', (c[5], 'Select Message Service', (('<service>', '0: SMS AT commands are compatible with GSM 07.05 Phase 2 version 4.7.0.\\n\\\n1: SMS AT commands are compatible with GSM 07.05 Phase 2 + version'),), None, 'The supported services include originated (SMS-MO) and terminated short messages (SMS-MT) as well as Cell Broadcast Message (SMS-CB) services.')),\n('AT+CNMA', (c[5], 'New Message Acknowledgment', (('<n>', '0: send RP-ACK without PDU (same as TEXT mode)\\n\\\n1: send RP-ACK with optional PDU message\\n2: send RP-ERROR with optional PDU message'),\n                                                  ('<length>', 'Length of the PDU message')), None, 'This command allows reception of a new message routed directly to the TE to be acknowledged.\\n\\\nIn TEXT mode, only positive acknowledgement to the network (RP-ACK) is possible.\\n\\\nIn PDU mode, either positive (RP-ACK) or negative (RP-ERROR) acknowledgement to the network is possible.\\n\\\nAcknowledgement with +CNMA is possible only if the +CSMS parameter is set to 1 (+CSMS=1) when a \\\n+CMT or +CDS indication is shown (see +CNMI command).\\n\\\nIf no acknowledgement occurs within the network timeout, an RP-ERROR is sent to the network. The <mt> \\\nand <ds> parameters of the +CNMI command are then reset to zero (do not show new message indication).')),\n('AT+CPMS', (c[5], 'Preferred Message Storage', (('<mem1>', 'Memory used to list, read and delete messages. It can be:\\n\\\n \"SM\": SMS message storage in SIM (default)\\n\\\n \"BM\": CBM message storage (in volatile memory).\\n\\\n \"SR\": Status Report message storage (in SIM if the EF-SMR file exists, otherwise in the ME non volatile memory)\\n\\\n       Note: \"SR\" ME non-volatile memory is cleared when another SIM card is inserted. It is kept, even after a reset, while the same SIM card is used.'),\n                                                 ('<mem2>', 'Memory to be used to write and send messages\\n \"SM\": SMS message storage in SIM (default)')),\n             (('<used1>', 'Used memory 1'),('total1', 'Total memory 1'),('<used2>', 'Used memory 2'),('total2', 'Total memory 2')),\n             'This command allows the message storage area to be selected (for reading, writing, etc).')),\n('AT+CMGF', (c[5], 'Preferred Message Format', (('<mode>', '0: PDU mode\\n1: Text mode'),), None, 'The message formats supported are text mode and PDU mode.')),\n('AT+CSAS', (c[5], 'Save Settings')),\n('AT+CRES', (c[5], 'Restore Settings')),\n('AT+CNMI', (c[5], 'New Message Indication', (('<mode>', 'Controls the processing of unsolicited result codes. Values:\\n\\\n0: Buffer unsolicited result codes in the TA. If TA result code buffer is full, indications can be buffered in \\\nsome other place, or the oldest indications may be discarded and replaced with the new received indications\\n\\\n1: Discard indication and reject new received message unsolicited result codes when TA-TE link is reserved. Otherwise forward them directly to the TE\\n\\\n2: Buffer unsolicited result codes in the TA when TA-TE link is reserved and flush them to the TE after reservation. Otherwise forward them directly to the TE\\n\\\n3: Forward unsolicited result codes directly to the TE. TA-TE link specific inband used to embed result codes and data when TA is in on-line data mode'),\n                                              ('<mt>', 'Sets the result code indication routing for SM-DELIVERs. Default is 0. Values:\\n\\\n0: No SMS-DELIVER indications are routed.\\n\\\n1: SMS-DELIVERs are routed using unsolicited code: +CMTI: \"SM\",<index>\\n\\\n2: SMS-DELIVERs (except class 2 messages) are routed using unsolicited code: +CMT: [<alpha>,]\\\n<length> <CR> <LF> <pdu> (PDU mode) or +CMT: <oa>,[<alpha>,] <scts> [,<tooa>, <fo>, <pid>, <dcs>,<sca>, <tosca>, <length>] <CR><LF><data> (text mode)\\n\\\n3: Class 3 SMS-DELIVERs are routed directly using code in <mt>=2 ; Message of other classes result in indication <mt>=1'),\n                                              ('<bm>', 'Set the rules for storing received CBMs (Cell Broadcast Message). Default is 0. Values:\\n\\\n0: No CBM indications are routed to the TE. The CBMs are stored.\\n\\\n1: The CBM is stored and an indication of the memory location is routed to the customer application using unsolicited result code: +CBMI: \"BM\", <index>\\n\\\n2: New CBMs are routed directly to the TE using unsolicited result code. +CBM: <length><CR><LF><pdu> (PDU mode) or +CBM:<sn>,<mid>,<dcs>,<page>,<pages>(Text mode) <CR><LF> <data>\\n\\\n3: Class 3 CBMs: as <bm>=2. Other classes CBMs: as <bm>=1.'),\n                                              ('<ds>', 'for SMS-STATUS-REPORTs. Default is 0. Values:\\n\\\n0: No SMS-STATUS-REPORTs are routed.\\n\\\n1: SMS-STATUS-REPORTs are routed using unsolicited code: +CDS: <length> <CR> <LF> <pdu> (PDU\\n\\\nmode) or +CDS: <fo>,<mr>, [<ra>] , [<tora>], <scts>,<dt>,<st> (Text mode)\\n\\\n2: SMS-STATUS-REPORTs are stored and routed using the unsolicited result code: +CDSI: \"SR\",<index>'),\n                                              ('<bfr>', 'Default is 0. Values:\\n\\\n0: TA buffer of unsolicited result codes defined within this command is flushed to the TE when <mode> 1...3\\\nis entered (OK response shall be given before flushing the codes)\\n\\\n1: TA buffer of unsolicited result codes defined within this command is cleared when <mode> 1...3 is entered.')),\n             None, 'This command selects the procedure for message reception from the network.')),\n('AT+CMGR', (c[5], 'Read Message', (('<index>', 'Location of message to read'),), None, 'This command allows the application to read stored messages. The messages are read from the memory selected by the +CPMS command.')),\n('AT+CMGL', (c[5], 'List Message', (('<stat>', 'Status of messages in memory to list. Allowed values are:\\n\\\nText mode     PDU mode\\n\\\n\"REC UNREAD\"  0\\n\\\n\"REC READ\"    1\\n\\\n\"STO UNSENT\"  2\\n\\\n\"STO SENT\"    3\\n\\\n\"ALL\"         4'),), None, 'This command allows the application to read stored messages, by indicating the type of the message to read. The messages are read from the memory selected by the +CPMS command.')),\n('AT+CMGS', (c[5], 'Send Message', (('<da>', 'Destination Address (text mode) - message destination phone number'),), None, 'The <address> field is the address of the terminal to which the message is sent. To send the message, \\\nsimply type, <ctrl-Z> character (ASCII 26). The text can contain all existing characters except <ctrl-Z> and \\\n<ESC> (ASCII 27). This command can be aborted using the <ESC> character when entering text. In PDU \\\nmode, only hexadecimal characters are used (\\'0\\'...\\'9\\',\\'A\\'...\\'F\\').')),\n('AT+CMGW', (c[5], 'Write Message to Memory')),\n('AT+CMSS', (c[5], 'Send Message from Storage', (('<index>', 'location of stored message'),\n                                                 ('<da>', 'destination address'),\n                                                 ('<toda>', 'type of destination address')),\n                                                (('<mr>', 'message reference'),), 'This command sends a message stored at location value <index>.')),\n('AT+CSMP', (c[5], 'Set Text Mode Parameters', (('<fo>', 'byte comprising 6 fields, RP, UDHI, SRR, VPFG, RD and MTI'),\n                                                ('<vp>', 'validity period'),\n                                                ('<pid>', 'used to indicate the higher layer protocol being used or indicates interworking with a certain type of \\\ntelematic device. For example, 0x22 is for group 3 telefax, 0x24 is for voice telephone, 0x25 is for ERMES.'),\n                                                ('<dcs>', 'used to determine the way the information is encoded.')), None, 'This command selects a value for <vp>, <pid>, and <dcs>.')),\n('AT+CMGD', (c[5], 'Delete Message', (('<index>', 'Integer type values in the range of location numbers of SIM Message memory when the preferred message storage is \"SM or \"SR\".'),\n                                      ('<DelFlag>', '0: Delete message at location <index>.\\n\\\n1: Delete All READ messages\\n\\\n2: Delete All READ and SENT messages\\n\\\n3: Delete All READ, SENT and UNSENT messages\\n\\\n4: Delete All messages.')), None, 'This command deletes one or several messages from preferred message storage.')),\n('AT+CSCA', (c[5], 'Service Center Address', (('<sca>', 'service center address'),), None, 'This command sets (or queries) the service center to which SMS messages must be sent.')),\n('AT+CSCB', (c[5], 'Select Cell Broadcast Message Types', (('<mode>', '0 - CBM reception activated\\n1 - CBM reception deactivated'), ('<mids>', 'message identifiers - indicates the type of message idenitfiers for which the ME should listen'), ('<dcss>', 'Supported Languages')), None, 'This command selects which types of CBMs are to be received by the ME.')),\n('AT+WCBM', (c[5], 'Cell Broadcast Message Identifiers')),\n('AT+WMSC', (c[5], 'Message Status Modification')),\n('AT+WMGO', (c[5], 'Message Overwriting')),\n('AT+WUSS', (c[5], 'Unchange SMS Status')),\n# Supplementary Services\n('AT+CCFC', (c[6], 'Call Forwarding')),\n('AT+CCWA', (c[6], 'Call Waiting')),\n('AT+CLIR', (c[6], 'Calling Line Identification Restriction', (('<n>', 'Sets the line ID restriction for outgoing calls. Values:\\n\\\n0: Presentation indicator is used according to the subscription of the CLIR service\\n\\\n1: CLIR invocation\\n2: CLIR suppression'),), (('<m>', 'Shows the subscriber CLIR status in the network. Values:\\n\\\n0: CLIR not provisioned\\n1: CLIR provisioned in permanent mode\\n2: Unknown (no network...)\\n3: CLIR temporary mode presentation restricted\\n4: CLIR temporary mode presentation allowed'),),\n             'This command controls the Calling Line Identification restriction supplementary service.')),\n('AT+CLIP', (c[6], 'Calling Line Identification Presentation',\n             (('<n>', 'Parameter sets/shows the result code presentation in the TA. Values:\\n0: Disable\\n1: Enable'),),\n             (('<m>', 'Parameter shows the subscriber CLIP service status in the network. Values:\\n0: CLIP not provisioned\\n1: CLIP provisioned\\n2: Unknown (no network...)'),),\n             'This command controls the calling line identification presentation supplementary service. When presentation \\\nof the CLI (Calling Line Identification) is enabled (and calling subscriber allows), +CLIP response is returned \\\nafter every RING (or +CRING) result code.')),\n('AT+COLP', (c[6], 'Connected Line Identification Presentation')),\n('AT+CAOC', (c[6], 'Advice Of Charge')),\n('AT+CAMM', (c[6], 'Accumulated Call Meter Maximum')),\n('AT+CPUC', (c[6], 'Price Per Unit and Currency Table')),\n('AT+CHLD', (c[6], 'Call Related Supplementary Services')),\n('AT+CHLD', (c[6], 'Call Related Supplementary Services')),\n('AT+CLCC', (c[6], 'List Current Calls', None, (('<idx>', 'integer type, call identification as described in GSM 02.30'),\n                                                ('<dir>', '0: mobile originated (MO) call\\n2: mobile terminated (MT) call'),\n                                                ('<stat>', '0: active\\n\\\n1: held\\n\\\n2: dialing (MO call)\\n\\\n3: alerting (MO call)\\n\\\n4: incoming (MT call)\\n\\\n5: waiting (MT call)'),\n                                                ('<mode>', '0: voice\\n\\\n1: data\\n\\\n2: fax\\n\\\n9: unknown'),\n                                                ('<mpty>', '0: call is not one of multiparty (conference) call parties\\n\\\n1: call is one of multiparty (conference) call parties'),\n                                                ('<number>', 'string type phone number in format specified by <type>'),\n                                                ('<type>', 'type of address byte in integer format'),\n                                                ('<alpha>', 'optional string type alphanumeric representation of <number> corresponding to the entry found in phonebook')),\n             'This command returns a list of current calls.')),\n('AT+CSSN', (c[6], 'Supplementary Service Notifications')),\n('AT+CUSD', (c[6], 'Unstructured Supplementary Service Data', (('<n>', '0: Disable the result code presentation\\n\\\n1: Enable the result code presentation\\n\\\n2: Cancel session (not applicable to read command response)'),\n                                                               ('<str>', 'network string, converted in the selected character set'),\n                                                               ('<dcs>', 'the data coding scheme received (TSM TS 03.38)')),\n             (('<m>', '0: no further user action required (network initiated USSD-Notify, or no further information needed after mobile initiated operation)\\n\\\n1: further user action required (network initiated USSD-Request, or further information needed after mobile initiated operation)\\n\\\n2: USSD terminated by network\\n\\\n4: Operation not supported'),),\n             'The USSD supplementary service is described in GSM 02.90. It is based on sequences of digits which may \\\nbe entered by a mobile user with a handset. A sequence entered is sent to the network which replies with an \\\nalphanumerical string, for display only, or for display plus request for the next sequence.\\n\\\nThis command is used to:\\n\\\n- Enable or disable the CUSD indication sent to the application by the product when an incoming USSD is received\\n\\\n- Send and receive USSD strings')),\n('AT+CCUG', (c[6], 'Closed User Group')),\n# Data\n('AT+CBST', (c[7], 'Bearer Type Selection')),\n('AT+FCLASS', (c[7], 'Select Mode', (('<n>', '0: Data\\n1: Fax class 1\\n2: Fax class 2'),), None, 'This command puts the modem into a particular operating mode (data or fax).')),\n('AT+CR', (c[7], 'Service Reporting Control')),\n('AT+CRC', (c[7], 'Cellular Result Dodes', (('<mode>', '0: Disable extended reports\\n1: Enable extended reports'),), None, 'This command shows more detailed ring information for an incoming call (voice or data). Instead of the string \\\n\"RING\", an extended string is used to indicate which type of call is ringing (e.g. +CRING: VOICE).\\n\\\nThese extended indications are:\\n\\\n+CRING: ASYNC     for asynchronous transparent\\n\\\n+CRING: REL ASYNC for asynchronous non-transparent\\n\\\n+CRING: VOICE     for normal speech.\\n\\\n+CRING: FAX       for fax calls')),\n('AT+ILRR', (c[7], 'DTE-DCE Local Rate Reporting')),\n('AT+CRLP', (c[7], 'Radio Link Protocol Parameters')),\n('AT+DOPT', (c[7], 'Radio Link Protocol Parameters')),\n('AT+CGDCONT', (c[7], 'Define PDP Context', (('<cid>', 'PDP Context Identifier - a numeric parameter (1-32) which specifies a particular \\\nPDP context definition. The parameter is local to the TE-MT interface and is used in \\\nother PDP context-related commands.'),\n                                             ('<PDP_type>', 'A string parameter which specifies the type of packet data protocol. (IP, IPV6, PPP, X.25 etc)'),\n                                             ('<APN>', 'Access Point Name. String parameter; logical name that is used to select the GGSN or external packet data network'),\n                                             ('<PDP_address>', 'String parameter that identifies the MT in the address space applicable to the PDP. If null/omitted, a dynamic address may be requested.'),\n                                             ('<d_comp>', 'PDP data compression. Values:\\n\\\n0 - off (default)\\n\\\n1 - on'),\n                                             ('<h_comp>', 'PDP header compression. Values:\\n\\\n0 - off (default)\\n\\\n1 - on')), None, 'This command specifies the PDP (Packet Data Protocol) context parameter values, such as PDP type (IP, IPV6, PPP, X.25 etc), APN, data compression, header compression, etc.')),\n('AT+CGATT', (c[7], 'GPRS attach or detach', (('<state>', 'indicates the state of GPRS attachment:\\n\\\n0 - detached\\n\\\n1 - attached\\n'),), None, 'The execution command is used to attach the MT to, or detach the MT from, the GPRS\\\nservice. After the command has completed, the MT remains in V.25ter command state.\\n\\\nAny active PDP contexts will be automatically deactivated when the attachment state changes to detached.')),\n\n('AT+CGACT', (c[7], 'PDP context activate or deactivate', (('<state>', 'indicates the state of PDP context activation:\\n\\\n0 - deactivated\\n\\\n1 - activated\\n'),\n                                                           ('<cid>', 'a numeric parameter which specifies a particular PDP context.')),\n               None, 'The execution command is used to activate or deactivate the specified PDP context (s).\\n\\\nAfter the command has completed, the MT remains in V.25ter command state.')),\n# Fax\n('AT+FTM', (c[8], 'Transmit Speed')),\n('AT+FRM', (c[8], 'Receive Speed')),\n('AT+FTH', (c[8], 'HDLC Transmit Speed')),\n('AT+FRH', (c[8], 'HDLC Receive Speed')),\n('AT+FTS', (c[8], 'Stop Transmission and Wait')),\n('AT+FRS', (c[8], 'Receive Silence')),\n# Fax Class 2\n('AT+FDT', (c[9], 'Transmit Data')),\n('AT+FDR', (c[9], 'Receive Data')),\n('AT+FET', (c[9], 'Transmit Page Punctuation')),\n('AT+FPTS', (c[9], 'Page Transfer Status Parameters')),\n('AT+FK', (c[9], 'Terminate Session')),\n('AT+FBOR', (c[9], 'Page Transfer Bit Order')),\n('AT+FBUF', (c[9], 'Buffer Size Report')),\n('AT+FCQ', (c[9], 'Copy Quality Checking')),\n('AT+FCR', (c[9], 'Capability to Receive')),\n('AT+FDIS', (c[9], 'Current Session Parameters')),\n('AT+FDCC', (c[9], 'DCE Capabilities Parameters')),\n('AT+FLID', (c[9], 'Local ID String')),\n('AT+FPHCTO', (c[9], 'Page Transfer Timeout Parameter')),\n# V24-V25\n('AT+IPR', (c[10], 'Fixed DTE Rate', (('<value>', 'Baud rates that can be used by the DCE.\\n0 enables autobauding on some modems.'),), None, 'This commands specifies the data rate at which the DCE will accept commands.')),\n('AT+ICF', (c[10], 'DTE-DCE Character Framing', (('<format>', '0: Autodetect\\n\\\n1: 8 Data 2 Stop\\n\\\n   <parity> parameter is ignored\\n\\\n2: 8 Data 1 Parity 1 Stop\\n\\\n   If no <parity> provided, 3 is used by default as <parity> value\\n\\\n3: 8 Data 1 Stop\\n\\\n   <parity> parameter is ignored\\n\\\n4: 7 Data 2 Stop\\n\\\n   <parity> parameter is ignored\\n\\\n5: 7 Data 1 Parity 1 Stop\\n\\\n   If no <parity> provided, 3 is used by default as <parity> value\\n\\\n6: 7 Data 1 Stop\\n\\\n   <parity> parameter is ignored'),\n                                                 ('<parity', '0: Odd\\n1: Even\\n2: Mark\\n3: Space\\n4: None')), None, 'This command determines the local serial port start-stop (asynchronous) character framing that the DCE uses.')),\n('AT+IFC', (c[10], 'DTE-DCE Flow Control')),\n('AT&C', (c[10], 'Set DCD Signal', (('<n>', '0: DCD always on\\n1: DCD matches the state of the remote modem\\'s data carrier'),), None, 'This commands controls the Data Carrier Detect (DCD) signal.')),\n('AT&D', (c[10], 'Set DTR Signal', (('<n>', '0: The DTR signal is ignored\\n1: Modem switches from data to command mode when DTR switches from ON to OFF\\n2: Upon DTR switch from ON to OFF, the call is released'),), None, 'This commands controls the Data Terminal Ready (DTR) signal.')),\n('AT&S', (c[10], 'Set DSR Signal', (('<n>', '0: DSR always on\\n1: DSR off in command mode. DSR on in data mode.'),), None, 'This commands controls the Data Set Ready (DSR) signal.')),\n('ATO', (c[10], 'Back to Online Mode', None, None, 'If a connection has been established and the ME is in command mode, this command allows you to return to online data mode.')),\n('ATQ', (c[10], 'Result Code Suppression', (('<n>', '0: DCE transmit result codes\\n1: Result codes are suppressed and not transmitted'),), None, 'This command determines whether the mobile equipment sends result codes or not.')),\n('ATV', (c[10], 'DCE Response Format', (('<n>', '0: DCE transmits limited headers and trailers and numeric result codes\\n1: DCE transmits full headers and trailers and verbose response text'),), None, 'This command determines the DCE response format, with or without header characters <CR><LF>. Result codes are provided as numeric or verbose.')),\n('ATZ', (c[10], 'Default Configuration', None, None, 'This command restores the configuration profile. Any call is released')),\n('AT&W', (c[10], 'Save Configuration', None, None, 'This command writes the active configuration to a non-volatile memory (EEPROM).')),\n('AT&T', (c[10], 'Auto-Tests')),\n('ATE', (c[10], 'Echo', (('<n>', '0: Characters are not echoed\\n1: Characters are echoed'),), None, 'This command is used to determine whether the modem echoes characters received by an external application (DTE).')),\n('AT&F', (c[10], 'Restore Factory Settings', (('<n>', '0: Restore factory settings'),), None, 'This command is used to restore the factory settings from EEPROM.')),\n('AT&V', (c[10], 'Display Configuration', (('<n>', '0 Displays the modem configuration in RAM. Default value if no parameter provided.\\n\\\n1: Displays the modem configuration in EEPROM.\\n\\\n2: Displays the modem factory configuration.'),), None, 'This command is used to display the modem configuration.')),\n('ATI', (c[10], 'Request Identification Information', (('<n>', '0: Displays manufacturer followed by model identification. Equivalent to +CGMI and +CGMM.\\n\\\n3: Displays revision identification. Equivalent to +CGMR.\\n\\\n4: Displays modem configuration in RAM. Equivalent to &V0.\\n\\\n5: Displays modem configuration in EEPROM. Equivalent to &V1.\\n\\\n6: Displays modem data features. Lists the supported data rates, data modes, and fax classes.\\n\\\n7: Displays modem voice features.'),), None, 'This command causes the product to transmit one or more lines of specific information text.')),\n('AT+WMUX', (c[10], 'Multiplexing Mode')),\n# Specific AT Commands\n('AT+CCED', (c[11], 'Cell Environment Description')),\n('AT+WIND', (c[11], 'General Indications')),\n('AT+ADC', (c[11], 'Analog Digital Converter Measurements')),\n('AT+CMER', (c[11], 'Mobile Equipment Event Reporting')),\n('AT+CIND', (c[11], 'Indicator Control')),\n('AT+CMEC', (c[11], 'Mobile Equipment Control Mode')),\n('AT+WLPR', (c[11], 'Read Language Preference')),\n('AT+WLPW', (c[11], 'Write Language Preference')),\n('AT+WIOR', (c[11], 'Read GPIO Value')),\n('AT+WIOW', (c[11], 'Write GPIO Value')),\n('AT+WIOM', (c[11], 'Input/Output Management')),\n('AT+WAC', (c[11], 'Abort Command')),\n('AT+WTONE', (c[11], 'Play Tone')),\n('AT+WDTMF', (c[11], 'Play DTMF Tone')),\n('AT+WDWL', (c[11], 'Downloading')),\n('AT+WVR', (c[11], 'Voice Rate')),\n('AT+WDR', (c[11], 'Data Rate')),\n('AT+WSVG', (c[11], 'Select Voice Gain')),\n('AT+WSTR', (c[11], 'Status Request')),\n)"
  },
  {
    "path": "tools/gsmtermlib/posoptparse.py",
    "content": "\"\"\" PosOptionParser class gotten from Douglas Mayle at StackOverflow:\nhttp://stackoverflow.com/a/664614/1980416\n\nUsed for positional argument support similar to argparse (for Python 2.6 compatibility)\n\"\"\"\n\nfrom optparse import OptionParser, Option, IndentedHelpFormatter\n\nclass PosOptionParser(OptionParser):\n    def format_help(self, formatter=None):\n        class Positional(object):\n            def __init__(self, args):\n                self.option_groups = []\n                self.option_list = args\n\n        positional = Positional(self.positional)\n        formatter = IndentedHelpFormatter()\n        formatter.store_option_strings(positional)\n        output = ['\\n', formatter.format_heading(\"Positional Arguments\")]\n        formatter.indent()\n        pos_help = [formatter.format_option(opt) for opt in self.positional]\n        pos_help = [line.replace('--','') for line in pos_help]\n        output += pos_help\n        return OptionParser.format_help(self, formatter) + ''.join(output)\n\n    def add_positional_argument(self, option):\n        try:\n            args = self.positional\n        except AttributeError:\n            args = []\n        args.append(option)\n        self.positional = args\n\n    def set_out(self, out):\n        self.out = out\n"
  },
  {
    "path": "tools/gsmtermlib/terminal.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\\\nGSMTerm: A user-friendly terminal for interacting with a GSM modem\n\nNote: The \"Console\" object was copied from pySerial's miniterm.py code\n\n@author: Francois Aucamp <francois.aucamp@gmail.com>\n\"\"\"\n\nfrom __future__ import print_function\nimport os, sys, threading, time\nimport serial\n\nfrom gsmmodem.serial_comms import SerialComms\nfrom .trie import Trie\nfrom gsmmodem.exceptions import TimeoutException\n\n# first choose a platform dependant way to read single characters from the console\nglobal console\n\nif os.name == 'nt':\n    import msvcrt\n    class Console(object):\n\n        CURSOR_UP = '{0}{1}'.format(chr(0xe0), chr(0x48))\n        CURSOR_DOWN = '{0}{1}'.format(chr(0xe0), chr(0x50))\n        CURSOR_LEFT = '{0}{1}'.format(chr(0xe0), chr(0x4b))\n        CURSOR_RIGHT = '{0}{1}'.format(chr(0xe0), chr(0x4d))\n        #TODO: find out what this in windows:\n        DELETE = ''\n        HOME = ''\n        END = ''\n\n        def __init__(self):\n            pass\n\n        def setup(self):\n            pass    # Do nothing for 'nt'\n\n        def cleanup(self):\n            pass    # Do nothing for 'nt'\n\n        def getkey(self):\n            while True:\n                z = msvcrt.getch()\n                if z == '\\xe0': # extended (cursor keys, etc)\n                    z += msvcrt.getch()\n                    return z\n                elif z == '\\0':    # functions keys, ignore\n                    msvcrt.getch()\n                else:\n                    if z == '\\r':\n                        return '\\n'\n                    return z\n\n    console = Console()\n\nelif os.name == 'posix':\n    import termios, tty\n    class Console(object):\n\n        CURSOR_UP = '{0}{1}{2}'.format(chr(27), chr(91), chr(65))\n        CURSOR_DOWN = '{0}{1}{2}'.format(chr(27), chr(91), chr(66))\n        CURSOR_LEFT = '{0}{1}{2}'.format(chr(27), chr(91), chr(68))\n        CURSOR_RIGHT = '{0}{1}{2}'.format(chr(27), chr(91), chr(67))\n        DELETE = '{0}{1}{2}{3}'.format(chr(27), chr(91), chr(51), chr(126))\n        HOME = '{0}{1}{2}'.format(chr(27), chr(79), chr(72))\n        END = '{0}{1}{2}'.format(chr(27), chr(79), chr(70))\n\n        def __init__(self):\n            self.fd = sys.stdin.fileno()\n\n        def setup(self):\n            self.old = termios.tcgetattr(self.fd)\n            new = termios.tcgetattr(self.fd)\n            new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG\n            new[6][termios.VMIN] = 1\n            new[6][termios.VTIME] = 0\n            termios.tcsetattr(self.fd, termios.TCSANOW, new)\n\n#        def setup(self):\n#            self.oldSettings = termios.tcgetattr(self.fd)\n#            tty.setraw(self.fd)\n\n        def getkey(self):\n            c = os.read(self.fd, 4)\n            #print (len(c))\n            #for a in c:\n            #    print('rx:',ord(a))\n            return c\n\n        def cleanup(self):\n            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)\n\n    console = Console()\n\n    def cleanup_console():\n        console.cleanup()\n\n    console.setup()\n    sys.exitfunc = cleanup_console      # terminal modes have to be restored on exit...\n\nelse:\n    raise NotImplementedError(\"Sorry no implementation for your platform (%s) available.\" % sys.platform)\n\n\nclass RawTerm(SerialComms):\n    \"\"\" \"Raw\" terminal - basically just copies console input to serial, and prints out anything read \"\"\"\n\n    EXIT_CHARACTER = '\\x1d'   # CTRL+]\n    WRITE_TERM = '\\r' # Write terminator character\n\n    def __init__(self, port, baudrate=9600):\n        super(RawTerm, self).__init__(port, baudrate, notifyCallbackFunc=self._handleModemNotification)\n        self.port = port\n        self.baudrate = baudrate\n        self.echo = True\n\n    def _handleModemNotification(self, lines):\n        for line in lines:\n            print(line)\n\n    def printStartMessage(self):\n        print('\\nRaw terminal connected to {0} at {1}bps.\\nPress CTRL+] to exit.\\n'.format(self.port, self.baudrate))\n\n    def start(self):\n        self.connect()\n        # Start input thread\n        self.alive = True\n        self.inputThread = threading.Thread(target=self._inputLoop)\n        self.inputThread.daemon = True\n        self.inputThread.start()\n        self.printStartMessage()\n\n    def stop(self):\n        self.alive = False\n        if threading.current_thread() != self.inputThread:\n            self.inputThread.join()\n        self.close()\n\n    def _inputLoop(self):\n        \"\"\" Loop and copy console->serial until EXIT_CHARCTER character is found. \"\"\"\n        try:\n            while self.alive:\n                try:\n                    c = console.getkey()\n                except KeyboardInterrupt:\n                    print('kbint')\n                    c = serial.to_bytes([3])\n                if c == self.EXIT_CHARACTER:\n                    self.stop()\n                elif c == '\\n':\n                    # Convert newline input into \\r\n                    self.serial.write(self.WRITE_TERM)\n                    if self.echo:\n                        # Locally just echo the real newline\n                        sys.stdout.write(c)\n                        sys.stdout.flush()\n                else:\n                    #print('writing: ', c)\n                    self.serial.write(c)\n                    if self.echo:\n                        sys.stdout.write(c)\n                        sys.stdout.flush()\n        except:\n            self.alive = False\n            raise\n\nclass GsmTerm(RawTerm):\n    \"\"\" User-friendly terminal for interacting with a GSM modem.\n\n    Some features: tab-completion, help\n    \"\"\"\n\n    PROMPT = 'GSM> '\n    SMS_PROMPT = '> '\n    EXIT_CHARACTER_2 = chr(4) # CTRL+D\n\n    BACKSPACE_CHARACTER = chr(127)\n    CTRL_Z_CHARACTER = chr(26) # Used when entering SMS messages with AT+CMGS\n    ESC_CHARACTER = chr(27) # Used to cancel entering SMS messages with AT+CMGS\n\n    RESET_SEQ = '\\033[0m'\n    COLOR_SEQ = '\\033[1;{0}m'\n    BOLD_SEQ = '\\033[1m'\n\n    # ANSI colour escapes\n    COLOR_RED = COLOR_SEQ.format(30+1)\n    COLOR_GREEN = COLOR_SEQ.format(30+2)\n    COLOR_YELLOW = COLOR_SEQ.format(30+3)\n    COLOR_BLUE = COLOR_SEQ.format(30+4)\n    COLOR_MAGENTA = COLOR_SEQ.format(30+5)\n    COLOR_WHITE = COLOR_SEQ.format(30+7)\n    COLOR_CYAN = COLOR_SEQ.format(30+6)\n\n    def __init__(self, port, baudrate=9600, useColor=True):\n        super(GsmTerm, self).__init__(port, baudrate)\n        self.inputBuffer = []\n        self.history = []\n        self.historyPos = 0\n        self.useColor = useColor\n        self.cursorPos = 0\n        if self.useColor:\n            self.PROMPT = self._color(self.COLOR_GREEN, self.PROMPT)\n            self.SMS_PROMPT = self._color(self.COLOR_GREEN, self.SMS_PROMPT)\n        self._initAtCommandsTrie()\n        # Flag that indicates whether the user is typing an SMS message's text\n        self._typingSms = False\n\n    def printStartMessage(self):\n#        self.stdscr.addstr('GSMTerm started. Press CTRL+] to exit.')\n        print('\\nGSMTerm connected to {0} at {1}bps.\\nPress CTRL+] or CTRL+D to exit.\\n'.format(self.port, self.baudrate))\n        self._refreshInputPrompt()\n\n    def _color(self, color, msg):\n        \"\"\" Converts a message to be printed to the user's terminal in red \"\"\"\n        if self.useColor:\n            return '{0}{1}{2}'.format(color, msg, self.RESET_SEQ)\n        else:\n            return msg\n\n    def _boldFace(self, msg):\n        \"\"\" Converts a message to be printed to the user's terminal in bold \"\"\"\n        return self._color(self.BOLD_SEQ, msg)\n\n    def _handleModemNotification(self, lines):\n        # Clear any input prompt\n        self._removeInputPrompt()\n        if self._typingSms:\n            self.PROMPT = self._color(self.COLOR_CYAN, lines[0])\n        if lines[-1] == 'ERROR':\n            print(self._color(self.COLOR_RED, '\\n'.join(lines)))\n        else:\n            print(self._color(self.COLOR_CYAN, '\\n'.join(lines)))\n        self._refreshInputPrompt()\n\n    def _addToHistory(self, command):\n        self.history.append(command)\n        if len(self.history) > 100:\n            self.history = self.history[1:]\n\n    def _inputLoop(self):\n        \"\"\" Loop and copy console->serial until EXIT_CHARCTER character is found. \"\"\"\n\n        # Switch statement for handling \"special\" characters\n        actionChars = {self.EXIT_CHARACTER: self._exit,\n                       self.EXIT_CHARACTER_2: self._exit,\n\n                       console.CURSOR_LEFT: self._cursorLeft,\n                       console.CURSOR_RIGHT: self._cursorRight,\n                       console.CURSOR_UP: self._cursorUp,\n                       console.CURSOR_DOWN: self._cursorDown,\n\n                       '\\n': self._doConfirmInput,\n                       '\\t': self._doCommandCompletion,\n\n                       self.CTRL_Z_CHARACTER: self._handleCtrlZ,\n                       self.ESC_CHARACTER: self._handleEsc,\n\n                       self.BACKSPACE_CHARACTER: self._handleBackspace,\n                       console.DELETE: self._handleDelete,\n                       console.HOME: self._handleHome,\n                       console.END: self._handleEnd}\n\n        try:\n            while self.alive:\n                try:\n                    c = console.getkey()\n                except KeyboardInterrupt:\n                    c = serial.to_bytes([3])\n                if c in actionChars:\n                    # Handle character directly\n                    actionChars[c]()\n                elif len(c) == 1 and self._isPrintable(c):\n                    self.inputBuffer.insert(self.cursorPos, c)\n                    self.cursorPos += 1\n                    self._refreshInputPrompt()\n                #else:\n                #    for a in c:\n                #        print('GOT:',a,'(',ord(a),')')\n        except:\n            self.alive = False\n            raise\n\n    def _handleCtrlZ(self):\n        \"\"\" Handler for CTRL+Z keypresses \"\"\"\n        if self._typingSms:\n            self.serial.write(''.join(self.inputBuffer))\n            self.serial.write(self.CTRL_Z_CHARACTER)\n            self._typingSms = False\n            self.inputBuffer = []\n            self.cursorPos = 0\n            sys.stdout.write('\\n')\n            self._refreshInputPrompt()\n\n    def _handleEsc(self):\n        \"\"\" Handler for CTRL+Z keypresses \"\"\"\n        if self._typingSms:\n            self.serial.write(self.ESC_CHARACTER)\n            self._typingSms = False\n            self.inputBuffer = []\n            self.cursorPos = 0\n\n    def _exit(self):\n        \"\"\" Shuts down the terminal (and app) \"\"\"\n        self._removeInputPrompt()\n        print(self._color(self.COLOR_YELLOW, 'CLOSING TERMINAL...'))\n        self.stop()\n\n    def _cursorLeft(self):\n        \"\"\" Handles \"cursor left\" events \"\"\"\n        if self.cursorPos > 0:\n            self.cursorPos -= 1\n            sys.stdout.write(console.CURSOR_LEFT)\n            sys.stdout.flush()\n\n    def _cursorRight(self):\n        \"\"\" Handles \"cursor right\" events \"\"\"\n        if self.cursorPos < len(self.inputBuffer):\n            self.cursorPos += 1\n            sys.stdout.write(console.CURSOR_RIGHT)\n            sys.stdout.flush()\n\n    def _cursorUp(self):\n        \"\"\" Handles \"cursor up\" events \"\"\"\n        if self.historyPos > 0:\n            self.historyPos -= 1\n            clearLen = len(self.inputBuffer)\n            self.inputBuffer = list(self.history[self.historyPos])\n            self.cursorPos = len(self.inputBuffer)\n            self._refreshInputPrompt(clearLen)\n\n    def _cursorDown(self):\n        \"\"\" Handles \"cursor down\" events \"\"\"\n        if self.historyPos < len(self.history)-1:\n            clearLen = len(self.inputBuffer)\n            self.historyPos += 1\n            self.inputBuffer = list(self.history[self.historyPos])\n            self.cursorPos = len(self.inputBuffer)\n            self._refreshInputPrompt(clearLen)\n\n    def _handleBackspace(self):\n        \"\"\" Handles backspace characters \"\"\"\n        if self.cursorPos > 0:\n            #print( 'cp:',self.cursorPos,'was:', self.inputBuffer)\n            self.inputBuffer = self.inputBuffer[0:self.cursorPos-1] + self.inputBuffer[self.cursorPos:]\n            self.cursorPos -= 1\n            #print ('cp:', self.cursorPos,'is:', self.inputBuffer)\n            self._refreshInputPrompt(len(self.inputBuffer)+1)\n\n    def _handleDelete(self):\n        \"\"\" Handles \"delete\" characters \"\"\"\n        if self.cursorPos < len(self.inputBuffer):\n            self.inputBuffer = self.inputBuffer[0:self.cursorPos] + self.inputBuffer[self.cursorPos+1:]\n            self._refreshInputPrompt(len(self.inputBuffer)+1)\n\n    def _handleHome(self):\n        \"\"\" Handles \"home\" character \"\"\"\n        self.cursorPos = 0\n        self._refreshInputPrompt(len(self.inputBuffer))\n\n    def _handleEnd(self):\n        \"\"\" Handles \"end\" character \"\"\"\n        self.cursorPos = len(self.inputBuffer)\n        self._refreshInputPrompt(len(self.inputBuffer))\n\n    def _doConfirmInput(self):\n        if self._typingSms:\n            # SMS messages are confirmed with CTRL+Z or canceled with ESC\n            inputStr = ''.join(self.inputBuffer[:self.cursorPos])\n            self.serial.write(inputStr)\n            self.inputBuffer = self.inputBuffer[self.cursorPos:]\n            self.cursorPos = 0\n            sys.stdout.write('\\n')\n            self._refreshInputPrompt()\n            return\n        # Convert newline input into \\r\\n\n        if len(self.inputBuffer) > 0:\n            inputStr = ''.join(self.inputBuffer).strip()\n            self.inputBuffer = []\n            self.cursorPos = 0\n            inputStrLen = len(inputStr)\n            if len(inputStr) > 0:\n                self._addToHistory(inputStr)\n                self.historyPos = len(self.history)\n            if inputStrLen > 2:\n                if inputStr[0] == '?': # ?COMMAND\n                    # Help requested with function\n                    self._printCommandHelp(inputStr[1:])\n                    return\n                elif inputStr[-1] == inputStr[-2] == '?': # COMMAND??\n                    # Help requested with function\n                    cmd = inputStr[:-3 if inputStr[-3] == '=' else -2]\n                    self._printCommandHelp(cmd)\n                    return\n            inputStrLower = inputStr.lower()\n            if inputStrLower.startswith('help'): # help COMMAND\n                # Alternative help invocation\n                self._printCommandHelp(inputStr[5:])\n                return\n            elif inputStrLower.startswith('ls'):\n                if inputStrLower == 'lscat':\n                    sys.stdout.write('\\n')\n                    for category in self.completion.categories:\n                        sys.stdout.write('{0}\\n'.format(category))\n                    self._refreshInputPrompt(len(self.inputBuffer))\n                    return\n                elif inputStrLower == 'ls':\n                    sys.stdout.write('\\n')\n                    for command in self.completion:\n                        sys.stdout.write('{0:<8} - {1}\\n'.format(command, self.completion[command][1]))\n                    self._refreshInputPrompt(len(self.inputBuffer))\n                    return\n                else:\n                    ls = inputStrLower.split(' ', 1)\n                    if len(ls) == 2:\n                        category = ls[1].lower()\n                        if category in [cat.lower() for cat in self.completion.categories]:\n                            sys.stdout.write('\\n')\n                            for command in self.completion:\n                                commandHelp = self.completion[command]\n                                if category == commandHelp[0].lower():\n                                    sys.stdout.write('{0:<8} - {1}\\n'.format(command, commandHelp[1]))\n                            self._refreshInputPrompt(len(self.inputBuffer))\n                            return\n            elif inputStrLower.startswith('load'):\n                # Load a file containing AT commands to issue\n                load = inputStr.split(' ', 1)\n                if len(load) == 2:\n                    filename = load[1].strip()\n                    try:\n                        f = open(filename, 'r')\n                    except IOError:\n                        sys.stdout.write('\\n{0}\\n'.format(self._color(self.COLOR_RED, 'File not found: \"{0}\"'.format(filename))))\n                        self._refreshInputPrompt(len(self.inputBuffer))\n                    else:\n                        atCommands = f.readlines()\n                        f.close()\n                        sys.stdout.write('\\n')\n                        for atCommand in atCommands:\n                            atCommand = atCommand.strip()\n                            if len(atCommand) > 0 and atCommand[0] != '#':\n                                self.inputBuffer = list(atCommand.strip())\n                                self._refreshInputPrompt(len(self.inputBuffer))\n                                self._doConfirmInput()\n                                time.sleep(0.1)\n                    return\n            if len(inputStr) > 0:\n                if inputStrLower.startswith('at+cmgs='):\n                    # Prepare for SMS input\n                    self._typingSms = True\n                    try:\n                        sys.stdout.write('\\n')\n                        sys.stdout.flush()\n                        response = self.write(inputStr + self.WRITE_TERM, waitForResponse=True, timeout=3, expectedResponseTermSeq='> ')\n                    except TimeoutException:\n                        self._typingSms = False\n                    else:\n                        sys.stdout.write(self._color(self.COLOR_YELLOW, 'Type your SMS message, and press CTRL+Z to send it or press ESC to cancel.\\n'))\n                        self.SMS_PROMPT = self._color(self.COLOR_GREEN, response[0])\n                    self._refreshInputPrompt()\n                    return\n                self.serial.write(inputStr)\n                self.serial.write(self.WRITE_TERM)\n        # Locally just echo the real newline\n        sys.stdout.write('\\n')\n        sys.stdout.flush()\n\n    def _printGeneralHelp(self):\n        sys.stdout.write(self._color(self.COLOR_WHITE, '\\n\\n== GSMTerm Help ==\\n\\n'))\n        sys.stdout.write('{0} Press the up & down arrow keys to move backwards or forwards through your command history.\\n\\n'.format(self._color(self.COLOR_YELLOW, 'Command History:')))\n        sys.stdout.write('{0} Press the TAB key to provide command completion suggestions. Press the TAB key after a command is fully typed (with or without a \"=\" character) to quickly see its syntax.\\n\\n'.format(self._color(self.COLOR_YELLOW, 'Command Completion:')))\n        sys.stdout.write('{0} Type a command, followed with two quesetion marks to access its documentation, e.g. \"<COMMAND>??\". Alternatively, precede the command with a question mark (\"?<COMMAND>\"), or type \"help <COMMAND>\".\\n\\n'.format(self._color(self.COLOR_YELLOW, 'Command Documentation:')))\n        sys.stdout.write('{0} Type \"ls [category]\" to list the available AT commands known to GSMTerm for the given category (or all commands if no category is specified).\\nType \"lscat\" to see a list of categories.\\n\\n'.format(self._color(self.COLOR_YELLOW, 'List Available Commands:')))\n        sys.stdout.write('{0} Type \"load <filename>\" to load and execute a file containing AT commands, separated by newlines, e.g. \"load ./myscript.txt\".\\n\\n'.format(self._color(self.COLOR_YELLOW, 'Load Script:')))\n        sys.stdout.write('To exit GSMTerm, press CTRL+] or CTRL+D.\\n\\n')\n        self._refreshInputPrompt(len(self.inputBuffer))\n\n    def _printCommandHelp(self, command=None):\n        if command == None or len(command.strip()) == 0:\n            # Print general help\n            self._printGeneralHelp()\n            return\n        try:\n            command = command.strip()\n            commandHelp = self.completion[command.upper()]\n        except KeyError:\n            noHelp = True\n        else:\n            noHelp = commandHelp == None\n        if noHelp:\n            sys.stdout.write('\\r No help available for: {0}\\n'.format(self._color(self.COLOR_WHITE, command)))\n        else:\n            sys.stdout.write('\\n\\n{0} ({1})\\n\\n'.format(self._color(self.COLOR_WHITE, commandHelp[1]), command))\n            sys.stdout.write('{0} {1}\\n'.format(self._color(self.COLOR_YELLOW, 'Category:'), commandHelp[0]))\n            if len(commandHelp) == 2:\n                sys.stdout.write('\\nNo detailed help available for this command.\\n\\n')\n                self._refreshInputPrompt(len(self.inputBuffer))\n                return\n            sys.stdout.write('{0} {1}\\n'.format(self._color(self.COLOR_YELLOW, 'Description:'), commandHelp[4]))\n\n            valuesIsEnum = len(commandHelp) >= 6\n            if valuesIsEnum: # \"Values\" is an enum of allowed values (not multiple variables); use custom label\n                sys.stdout.write('{0} '.format(self._color(self.COLOR_YELLOW, commandHelp[5])))\n            else:\n                sys.stdout.write('{0} '.format(self._color(self.COLOR_YELLOW, 'Values:')))\n            commandValues = commandHelp[2]\n            syntax = [self._color(self.COLOR_WHITE, command)]\n            if commandValues != None:\n                if '+' in command or command.upper() in ['ATS0']:\n                    syntax.append(self._color(self.COLOR_WHITE, '='))\n                sys.stdout.write('\\n')\n                first = True\n                for value, valueDesc in commandValues:\n                    if first:\n                        first = False\n                    else:\n                        syntax.append(',' if not valuesIsEnum else '|')\n                    syntax.append(self._color(self.COLOR_MAGENTA, value))\n                    sys.stdout.write(' {0} {1}\\n'.format(self._color(self.COLOR_MAGENTA, value), valueDesc.replace('\\n', '\\n' + ' ' * (len(value) + 2)) if valueDesc != None else ''))\n            else:\n                sys.stdout.write('No parameters.\\n')\n            returnValues = commandHelp[3]\n            if returnValues != None:\n                sys.stdout.write('{0} '.format(self._color(self.COLOR_YELLOW, 'Response Values:')))\n                sys.stdout.write('\\n')\n                for value, valueDesc in returnValues:\n                    sys.stdout.write(' {0} {1}\\n'.format(self._color(self.COLOR_CYAN, value), valueDesc.replace('\\n', '\\n' + ' ' * (len(value) + 2)) if valueDesc != None else ''))\n            sys.stdout.write('{0}\\n {1}\\n\\n'.format(self._color(self.COLOR_YELLOW, 'Command Syntax:'), ''.join(syntax)))\n        self._refreshInputPrompt(len(self.inputBuffer))\n\n    def _doCommandCompletion(self):\n        \"\"\" Command-completion method \"\"\"\n        prefix =  ''.join(self.inputBuffer).strip().upper()\n        matches = self.completion.keys(prefix)\n        matchLen = len(matches)\n        if matchLen == 0 and prefix[-1] == '=':\n            try:\n                command = prefix[:-1]\n            except KeyError:\n                pass\n            else:\n                self.__printCommandSyntax(command)\n        elif matchLen > 0:\n            if matchLen == 1:\n                if matches[0] == prefix:\n                    # User has already entered command - show command syntax\n                    self.__printCommandSyntax(prefix)\n                else:\n                    # Complete only possible command\n                    self.inputBuffer = list(matches[0])\n                    self.cursorPos = len(self.inputBuffer)\n                    self._refreshInputPrompt(len(self.inputBuffer))\n                return\n            else:\n                commonPrefix = self.completion.longestCommonPrefix(''.join(self.inputBuffer))\n                self.inputBuffer = list(commonPrefix)\n                self.cursorPos = len(self.inputBuffer)\n                if matchLen > 20:\n                    matches = matches[:20]\n                    matches.append('... ({0} more)'.format(matchLen - 20))\n            sys.stdout.write('\\n')\n            for match in matches:\n                sys.stdout.write(' {0} '.format(match))\n            sys.stdout.write('\\n')\n            sys.stdout.flush()\n            self._refreshInputPrompt(len(self.inputBuffer))\n\n    def __printCommandSyntax(self, command):\n        \"\"\" Command-completion helper method: print command syntax \"\"\"\n        commandHelp = self.completion[command]\n        if commandHelp != None and len(commandHelp) > 2:\n            commandValues = commandHelp[2]\n            #commandDefault = commandHelp[3]\n            displayHelp = [self._color(self.COLOR_WHITE, command)]\n            if commandValues != None:\n                valuesIsEnum = len(commandHelp) >= 6\n                if '+' in command or command.upper() in ['ATS0']:\n                    displayHelp.append(self._color(self.COLOR_WHITE, '='))\n                displayHelp.append(('|' if valuesIsEnum else ',').join([value[0] for value in commandValues]))\n            sys.stdout.write('\\r Syntax: {0}\\n'.format(self._color(self.COLOR_WHITE, ''.join(displayHelp))))\n            sys.stdout.flush()\n            self._refreshInputPrompt(len(self.inputBuffer))\n\n    def _isPrintable(self, char):\n        return 33 <= ord(char) <= 126 or char.isspace()\n\n    def _refreshInputPrompt(self, clearLen=0):\n        termPrompt = self.SMS_PROMPT if self._typingSms else self.PROMPT\n        endPoint = clearLen if clearLen > 0 else len(self.inputBuffer)\n        sys.stdout.write('\\r{0}{1}{2}{3}'.format(termPrompt, ''.join(self.inputBuffer), (clearLen - len(self.inputBuffer)) * ' ', console.CURSOR_LEFT * (endPoint - self.cursorPos)))\n        sys.stdout.flush()\n\n    def _removeInputPrompt(self):\n        termPrompt = self.SMS_PROMPT if self._typingSms else self.PROMPT\n        sys.stdout.write('\\r{0}\\r'.format(' ' * (len(termPrompt) + len(self.inputBuffer))))\n\n    def _initAtCommandsTrie(self):\n        self.completion = Trie()\n        from .atcommands import ATCOMMANDS, CATEGORIES\n        for command, help in ATCOMMANDS:\n            if help != None:\n                self.completion[command] = help\n            else:\n                self.completion[command] = None\n        self.completion.categories = CATEGORIES\n\n"
  },
  {
    "path": "tools/gsmtermlib/trie.py",
    "content": "\"\"\" Pure Python trie implementation for strings \"\"\"\n\n# Compensate for differences between Python 2 and 3\nimport sys\nif sys.version_info[0] >= 3:\n    dictKeysIter = dict.keys\n    dictItemsIter = dict.items\n    dictValuesIter = dict.values\nelse: #pragma: no cover\n    dictKeysIter = dict.iterkeys\n    dictItemsIter = dict.iteritems\n    dictValuesIter = dict.itervalues\n\n\nclass Trie(object):\n\n    def __init__(self, key=None, value=None):\n        self.slots = {}\n        self.key = key\n        self.value = value\n\n    def __setitem__(self, key, value):\n        if key == None:\n            raise ValueError('Key may not be None')\n\n        if len(key) == 0:\n            # All of the original key's chars have been nibbled away\n            self.value = value\n            self.key = ''\n            return\n\n        c = key[0]\n\n        if c not in self.slots:\n            # Unused slot - no collision\n            if self.key != None and len(self.key) > 0:\n                # This was a \"leaf\" previously - create a new branch for its current value\n                branchC = self.key[0]\n                branchKey = self.key[1:] if len(self.key) > 1 else ''\n                self.slots[branchC] = Trie(branchKey, self.value)\n                self.key = None\n                self.value = None\n                if branchC != c:\n                    self.slots[c] = Trie(key[1:], value)\n                else:\n                    self.slots[c][key[1:]] = value\n            else:\n                # Store specified value in a new branch and return\n                self.slots[c] = Trie(key[1:], value)\n        else:\n            trie = self.slots[c]\n            trie[key[1:]] = value\n\n\n    def __delitem__(self, key):\n        if key == None:\n            raise ValueError('Key may not be None')\n        if len(key) == 0:\n            if self.key == '':\n                self.key = None\n                self.value = None\n                return\n            else:\n                raise KeyError(key)\n        c = key[0]\n        if c in self.slots:\n            trie = self.slots[c]\n            if key[1:] == trie.key:\n                if len(trie.slots) > 0:\n                    trie.key = None\n                    trie.value = None\n                else:\n                    del self.slots[c] # Remove the node\n            else:\n                del trie[key[1:]]\n        else:\n            raise KeyError(key)\n\n    def __getitem__(self, key):\n        if key == None:\n            raise ValueError('Key may not be None')\n        if len(key) == 0:\n            if self.key == '':\n                # All of the original key's chars have ben nibbled away\n                return self.value\n            else:\n                raise KeyError(key)\n        c = key[0]\n        if c in self.slots:\n            trie = self.slots[c]\n            return trie[key[1:]]\n        elif key == self.key:\n            return self.value\n        else:\n            raise KeyError(key)\n\n    def __contains__(self, key):\n        try:\n            self.__getitem__(key)\n        except KeyError:\n            return False\n        return True\n\n    def __len__(self):\n        global dictValuesIter\n        n = 1 if self.key != None else 0\n        for trie in dictValuesIter(self.slots):\n            n += len(trie)\n        return n\n\n    def get(self, key, default=None):\n        try:\n            return self.__getitem__(key)\n        except KeyError:\n            return default\n\n    def _allKeys(self, prefix):\n        \"\"\" Private implementation method. Use keys() instead. \"\"\"\n        global dictItemsIter\n        result = [prefix + self.key] if self.key != None else []\n        for key, trie in dictItemsIter(self.slots):\n            result.extend(trie._allKeys(prefix + key))\n        return result\n\n    def keys(self, prefix=None):\n        \"\"\" Return all or possible keys in this trie\n\n        If prefix is None, return all keys.\n        If prefix is a string, return all keys that start with this string\n        \"\"\"\n        if prefix == None:\n            return self._allKeys('')\n        else:\n            return self._filteredKeys(prefix, '')\n\n    def _filteredKeys(self, key, prefix):\n        global dictKeysIter\n        global dictItemsIter\n        if len(key) == 0:\n            result = [prefix + self.key] if self.key != None else []\n            for c, trie in dictItemsIter(self.slots):\n                result.extend(trie._allKeys(prefix + c))\n        else:\n            c = key[0]\n            if c in dictKeysIter(self.slots):\n                result = []\n                trie = self.slots[c]\n                result.extend(trie._filteredKeys(key[1:], prefix+c))\n            else:\n                result = [prefix + self.key] if self.key != None and self.key.startswith(key) else []\n        return result\n\n    def longestCommonPrefix(self, prefix=''):\n        \"\"\" Return the longest common prefix shared by all keys that start with prefix\n        (note: the return value will always start with the specified prefix)\n        \"\"\"\n        return self._longestCommonPrefix(prefix, '')\n\n    def _longestCommonPrefix(self, key, prefix):\n        if len(key) == 0:\n            if self.key != None:\n                return prefix + self.key\n            else:\n                slotKeys = list(self.slots.keys())\n                if len(slotKeys) == 1:\n                    c = slotKeys[0]\n                    return self.slots[c]._longestCommonPrefix('', prefix + c)\n                else:\n                    return prefix\n        elif self.key != None:\n            if self.key.startswith(key):\n                return prefix + self.key\n            else:\n                return '' # nothing starts with the specified prefix\n        else:\n            c = key[0]\n            if c in self.slots:\n                return self.slots[c]._longestCommonPrefix(key[1:], prefix + c)\n            else:\n                return '' # nothing starts with the specified prefix\n\n    def __iter__(self):\n        for k in list(self.keys()):\n            yield k\n        raise StopIteration\n"
  },
  {
    "path": "tools/identify-modem.py",
    "content": "#!/usr/bin/env python\n\n\n\"\"\"\\\nSimple script to assist with identifying a GSM modem\nThe debug information obtained by this script (when using -d) can be used\nto aid test cases (since I don't have access to every modem in the world ;-) )\n\n@author: Francois Aucamp <francois.aucamp@gmail.com>\n\"\"\"\nfrom __future__ import print_function\nimport sys\n\nfrom gsmmodem.modem import GsmModem\nfrom gsmmodem.exceptions import TimeoutException, PinRequiredError, IncorrectPinError\n\ndef parseArgs():\n    \"\"\" Argument parser for Python 2.7 and above \"\"\"\n    from argparse import ArgumentParser\n    parser = ArgumentParser(description='Identify and debug attached GSM modem')\n    parser.add_argument('port', metavar='PORT', help='port to which the GSM modem is connected; a number or a device name.')\n    parser.add_argument('-b', '--baud', metavar='BAUDRATE', default=115200, help='set baud rate')\n    parser.add_argument('-p', '--pin', metavar='PIN', default=None, help='SIM card PIN')\n    parser.add_argument('-d', '--debug',  action='store_true', help='dump modem debug information (for python-gsmmodem development)')\n    parser.add_argument('-w', '--wait', type=int, default=0, help='Wait for modem to start, in seconds')\n    return parser.parse_args()\n\ndef parseArgsPy26():\n    \"\"\" Argument parser for Python 2.6 \"\"\"\n    from gsmtermlib.posoptparse import PosOptionParser, Option\n    parser = PosOptionParser(description='Identify and debug attached GSM modem')\n    parser.add_positional_argument(Option('--port', metavar='PORT', help='port to which the GSM modem is connected; a number or a device name.'))\n    parser.add_option('-b', '--baud', metavar='BAUDRATE', default=115200, help='set baud rate')\n    parser.add_option('-p', '--pin', metavar='PIN', default=None, help='SIM card PIN')\n    parser.add_option('-d', '--debug',  action='store_true', help='dump modem debug information (for python-gsmmodem development)')\n    parser.add_option('-w', '--wait', type=int, default=0, help='Wait for modem to start, in seconds')\n    options, args = parser.parse_args()\n    if len(args) != 1:\n        parser.error('Incorrect number of arguments - please specify a PORT to connect to, e.g. {0} /dev/ttyUSB0'.format(sys.argv[0]))\n    else:\n        options.port = args[0]\n        return options\n\ndef main():\n    args = parseArgsPy26() if sys.version_info[0] == 2 and sys.version_info[1] < 7 else parseArgs()\n    print ('args:',args)\n    modem = GsmModem(args.port, args.baud)\n\n    print('Connecting to GSM modem on {0}...'.format(args.port))\n    try:\n        modem.connect(args.pin, waitingForModemToStartInSeconds=args.wait)\n    except PinRequiredError:\n        sys.stderr.write('Error: SIM card PIN required. Please specify a PIN with the -p argument.\\n')\n        sys.exit(1)\n    except IncorrectPinError:\n        sys.stderr.write('Error: Incorrect SIM card PIN entered.\\n')\n        sys.exit(1)\n\n    if args.debug:\n        # Print debug info\n        print('\\n== MODEM DEBUG INFORMATION ==\\n')\n        print('ATI', modem.write('ATI', parseError=False))\n        print('AT+CGMI:', modem.write('AT+CGMI', parseError=False))\n        print('AT+CGMM:', modem.write('AT+CGMM', parseError=False))\n        print('AT+CGMR:', modem.write('AT+CGMR', parseError=False))\n        print('AT+CFUN=?:', modem.write('AT+CFUN=?', parseError=False))\n        print('AT+WIND=?:', modem.write('AT+WIND=?', parseError=False))\n        print('AT+WIND?:', modem.write('AT+WIND?', parseError=False))\n        print('AT+CPMS=?:', modem.write('AT+CPMS=?', parseError=False))\n        print('AT+CNMI=?:', modem.write('AT+CNMI=?', parseError=False))\n        print('AT+CVHU=?:', modem.write('AT+CVHU=?', parseError=False))\n        print('AT+CSMP?:', modem.write('AT+CSMP?', parseError=False))\n        print('AT+GCAP:', modem.write('AT+GCAP', parseError=False))\n        print('AT+CPIN?', modem.write('AT+CPIN?', parseError=False))\n        print('AT+CLAC:', modem.write('AT+CLAC', parseError=False))\n        print()\n    else:\n        # Print basic info\n        print('\\n== MODEM INFORMATION ==\\n')\n        print('Manufacturer:', modem.manufacturer)\n        print('Model:', modem.model)\n        print('Revision:', modem.revision if modem.revision != None else 'N/A')\n        print('\\nIMEI:', modem.imei if modem.imei != None else 'N/A')\n        print('IMSI:', modem.imsi if modem.imsi != None else 'N/A')\n        print('\\nNetwork:', modem.networkName)\n        print('Signal strength:', modem.signalStrength)\n        print()\n\nif __name__ == '__main__':\n    main()\n\n"
  },
  {
    "path": "tools/sendsms.py",
    "content": "#!/usr/bin/env python\n\n\n\"\"\"\\\nSimple script to send an SMS message\n\n@author: Francois Aucamp <francois.aucamp@gmail.com>\n\"\"\"\nfrom __future__ import print_function\nimport sys, logging\n\nfrom gsmmodem.modem import GsmModem, SentSms\nfrom gsmmodem.exceptions import TimeoutException, PinRequiredError, IncorrectPinError\n\ndef parseArgs():\n    \"\"\" Argument parser for Python 2.7 and above \"\"\"\n    from argparse import ArgumentParser\n    parser = ArgumentParser(description='Simple script for sending SMS messages')\n    parser.add_argument('-i', '--port', metavar='PORT', help='port to which the GSM modem is connected; a number or a device name.')\n    parser.add_argument('-l', '--lock-path', metavar='PATH', help='Use oslo.concurrency to prevent concurrent access to modem')\n    parser.add_argument('-b', '--baud', metavar='BAUDRATE', default=115200, help='set baud rate')\n    parser.add_argument('-p', '--pin', metavar='PIN', default=None, help='SIM card PIN')\n    parser.add_argument('-d', '--deliver', action='store_true', help='wait for SMS delivery report')\n    parser.add_argument('-w', '--wait', type=int, default=0, help='Wait for modem to start, in seconds')\n    parser.add_argument('--CNMI', default='', help='Set the CNMI of the modem, used for message notifications')\n    parser.add_argument('--debug', action='store_true', help='turn on debug (serial port dump)')\n    parser.add_argument('destination', metavar='DESTINATION', help='destination mobile number')\n    parser.add_argument('message', nargs='?', metavar='MESSAGE', help='message to send, defaults to stdin-prompt')\n    return parser.parse_args()\n\ndef parseArgsPy26():\n    \"\"\" Argument parser for Python 2.6 \"\"\"\n    from gsmtermlib.posoptparse import PosOptionParser, Option\n    parser = PosOptionParser(description='Simple script for sending SMS messages')\n    parser.add_option('-i', '--port', metavar='PORT', help='port to which the GSM modem is connected; a number or a device name.')\n    parser.add_option('-b', '--baud', metavar='BAUDRATE', default=115200, help='set baud rate')\n    parser.add_option('-p', '--pin', metavar='PIN', default=None, help='SIM card PIN')\n    parser.add_option('-d', '--deliver', action='store_true', help='wait for SMS delivery report')\n    parser.add_option('-w', '--wait', type=int, default=0, help='Wait for modem to start, in seconds')\n    parser.add_option('--CNMI', default='', help='Set the CNMI of the modem, used for message notifications')\n    parser.add_positional_argument(Option('--destination', metavar='DESTINATION', help='destination mobile number'))\n    options, args = parser.parse_args()\n    if len(args) != 1:\n        parser.error('Incorrect number of arguments - please specify a DESTINATION to send to, e.g. {0} 012789456'.format(sys.argv[0]))\n    else:\n        options.destination = args[0]\n        options.message = None\n        options.lock_path = None\n        return options\n\ndef main():\n    args = parseArgsPy26() if sys.version_info[0] == 2 and sys.version_info[1] < 7 else parseArgs()\n    if args.port == None:\n        sys.stderr.write('Error: No port specified. Please specify the port to which the GSM modem is connected using the -i argument.\\n')\n        sys.exit(1)\n\n    if args.lock_path is None:\n        send_sms(args)\n    else:\n        try:\n            from oslo_concurrency import lockutils\n        except ImportError:\n            print('oslo_concurrency package is missing')\n            sys.exit(1)\n        # apply `lockutils.synchronized` decorator and run\n        decorator = lockutils.synchronized('python_gsmmodem_sendsms', external=True, lock_path=args.lock_path)\n        decorator(send_sms)(args)\n\n\ndef send_sms(args):\n    modem = GsmModem(args.port, args.baud, AT_CNMI=args.CNMI)\n    if args.debug:\n        # enable dump on serial port\n        logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)\n    \n    print('Connecting to GSM modem on {0}...'.format(args.port))\n    try:\n        modem.connect(args.pin, waitingForModemToStartInSeconds=args.wait)\n    except PinRequiredError:\n        sys.stderr.write('Error: SIM card PIN required. Please specify a PIN with the -p argument.\\n')\n        sys.exit(1)\n    except IncorrectPinError:\n        sys.stderr.write('Error: Incorrect SIM card PIN entered.\\n')\n        sys.exit(1)\n    print('Checking for network coverage...')\n    try:\n        modem.waitForNetworkCoverage(5)\n    except TimeoutException:\n        print('Network signal strength is not sufficient, please adjust modem position/antenna and try again.')\n        modem.close()\n        sys.exit(1)\n    else:\n        if args.message is None:\n            print('\\nPlease type your message and press enter to send it:')\n            text = raw_input('> ')\n        else:\n            text = args.message\n        if args.deliver:\n            print ('\\nSending SMS and waiting for delivery report...')\n        else:\n            print('\\nSending SMS message...')\n        try:\n            sms = modem.sendSms(args.destination, text, waitForDeliveryReport=args.deliver)\n        except TimeoutException:\n            print('Failed to send message: the send operation timed out')\n            modem.close()\n            sys.exit(1)\n        else:\n            modem.close()\n            if sms.report:\n                print('Message sent{0}'.format(' and delivered OK.' if sms.status == SentSms.DELIVERED else ', but delivery failed.'))\n            else:\n                print('Message sent.')\n\nif __name__ == '__main__':\n    main()\n\n"
  }
]