Thomas Verchow 5 months ago
parent
commit
307348fa8a
4 changed files with 127 additions and 166 deletions
  1. 0 10
      CHANGELOG.rst
  2. 4 7
      setup.cfg
  3. 123 0
      src/rfidacd/daemon.py
  4. 0 149
      src/rfidacd/skeleton.py

+ 0 - 10
CHANGELOG.rst

@@ -1,10 +0,0 @@
-=========
-Changelog
-=========
-
-Version 0.1
-===========
-
-- Feature A added
-- FIX: nasty bug #1729 fixed
-- add your changes here!

+ 4 - 7
setup.cfg

@@ -22,7 +22,7 @@ project_urls =
 #    Twitter = https://twitter.com/PyScaffold
 
 # Change if running only on Windows, Mac or Linux (comma-separated)
-platforms = any
+platforms = Linux
 
 # Add here all kinds of additional classifiers as defined under
 # https://pypi.python.org/pypi?%3Aaction=list_classifiers
@@ -47,6 +47,7 @@ package_dir =
 # For more information, check out https://semver.org/.
 install_requires =
     importlib-metadata; python_version<"3.8"
+    evdev
 
 
 [options.packages.find]
@@ -69,12 +70,8 @@ testing =
 # Add here console scripts like:
 # console_scripts =
 #     script_name = rfidacd.module:function
-# For example:
-# console_scripts =
-#     fibonacci = rfidacd.skeleton:run
-# And any other entry points, for example:
-# pyscaffold.cli =
-#     awesome = pyscaffoldext.awesome.extension:AwesomeExtension
+console_scripts =
+    rfidacd = rfidacd.daemon:run
 
 [tool:pytest]
 # Specify command line options as you would do when invoking pytest directly.

+ 123 - 0
src/rfidacd/daemon.py

@@ -0,0 +1,123 @@
+"""
+"""
+
+import argparse
+import logging
+import os
+import subprocess
+import sys
+from datetime import datetime as dt
+
+from evdev import InputDevice, ecodes
+
+from rfidacd import __version__
+
+__author__ = "Thomas Verchow"
+__copyright__ = "Thomas Verchow"
+__license__ = "MIT"
+
+_logger = logging.getLogger(__name__)
+
+SHELL = os.getenv("SHELL", "/bin/sh")
+
+
+def parse_args(args):
+    """
+    """
+
+    def dir_path(path: str):
+        if os.path.isdir(path):
+            return path
+        else:
+            parser.error(f"{path} is not a readable directory")
+
+    parser = argparse.ArgumentParser(description="RFID action commander daemon")
+    parser.add_argument(
+        "--version", action="version", version="rfidacd {ver}".format(ver=__version__),
+    )
+    parser.add_argument(
+        "-d", "--device", help="device to listen", type=argparse.FileType("r")
+    )
+    parser.add_argument(
+        "-p", "--path", default=".", help="path to action scripts", type=dir_path
+    )
+    parser.add_argument(
+        "-s", "--seconds", default=5, help="seconds between same rfid", type=int
+    )
+    parser.add_argument(
+        "-v",
+        "--verbose",
+        dest="loglevel",
+        help="set loglevel to INFO",
+        action="store_const",
+        const=logging.INFO,
+    )
+    parser.add_argument(
+        "-vv",
+        "--very-verbose",
+        dest="loglevel",
+        help="set loglevel to DEBUG",
+        action="store_const",
+        const=logging.DEBUG,
+    )
+    return parser.parse_args(args)
+
+
+def read_rfid(device):
+    combined_string = ""
+    for event in InputDevice(device.name).read_loop():
+        if event.type == ecodes.EV_KEY and event.value == 0:  # value 0 = release key
+            if event.code == 28:  # code 28 = KEY_ENTER
+                return combined_string
+            # [4:5]? .. KEY[] starts with 'KEY_' and we expect one char
+            combined_string += ecodes.KEY[event.code][4:5]
+
+
+def run_action(script: str):
+    _logger.info(f"RFID action: run '{SHELL} {script}'")
+    try:
+        r = subprocess.call([SHELL, script])
+    except Exception as err:
+        _logger.info(err)
+        last_rfid = None
+
+
+def main(args):
+    args = parse_args(args)
+    logging.basicConfig(level=args.loglevel)
+    _logger.debug(f"Device to read from: {args.device}")
+    _logger.debug(f"Shell to execute commands: {SHELL}")
+    _logger.debug(f"Expect scripts to run in {args.path}")
+
+    last_rfid = None
+    last_ts = dt.now()
+    while True:
+        rfid = read_rfid(args.device)
+        seconds_gone = (dt.now() - last_ts).total_seconds()
+
+        script = os.path.join(args.path, rfid)
+
+        if os.path.islink(script):  # special RFID - always run!
+            _logger.info(f"Action script {script} is special/link.")
+            run_action(script)
+            continue
+
+        if not os.path.isfile(script):
+            _logger.info(f"Action script {script} not found.")
+            continue
+
+        if rfid == last_rfid and seconds_gone <= args.seconds:
+            _logger.info(f"Same RFID after {seconds_gone:.1f} seconds. Do nothing.")
+            continue
+
+        last_ts = dt.now()
+        last_rfid = rfid
+        run_action(script)
+
+
+def run():
+    main(sys.argv[1:])
+
+
+if __name__ == "__main__":
+    run()

+ 0 - 149
src/rfidacd/skeleton.py

@@ -1,149 +0,0 @@
-"""
-This is a skeleton file that can serve as a starting point for a Python
-console script. To run this script uncomment the following lines in the
-``[options.entry_points]`` section in ``setup.cfg``::
-
-    console_scripts =
-         fibonacci = rfidacd.skeleton:run
-
-Then run ``pip install .`` (or ``pip install -e .`` for editable mode)
-which will install the command ``fibonacci`` inside your current environment.
-
-Besides console scripts, the header (i.e. until ``_logger``...) of this file can
-also be used as template for Python modules.
-
-Note:
-    This skeleton file can be safely removed if not needed!
-
-References:
-    - https://setuptools.readthedocs.io/en/latest/userguide/entry_point.html
-    - https://pip.pypa.io/en/stable/reference/pip_install
-"""
-
-import argparse
-import logging
-import sys
-
-from rfidacd import __version__
-
-__author__ = "Thomas Verchow"
-__copyright__ = "Thomas Verchow"
-__license__ = "MIT"
-
-_logger = logging.getLogger(__name__)
-
-
-# ---- Python API ----
-# The functions defined in this section can be imported by users in their
-# Python scripts/interactive interpreter, e.g. via
-# `from rfidacd.skeleton import fib`,
-# when using this Python module as a library.
-
-
-def fib(n):
-    """Fibonacci example function
-
-    Args:
-      n (int): integer
-
-    Returns:
-      int: n-th Fibonacci number
-    """
-    assert n > 0
-    a, b = 1, 1
-    for i in range(n - 1):
-        a, b = b, a + b
-    return a
-
-
-# ---- CLI ----
-# The functions defined in this section are wrappers around the main Python
-# API allowing them to be called directly from the terminal as a CLI
-# executable/script.
-
-
-def parse_args(args):
-    """Parse command line parameters
-
-    Args:
-      args (List[str]): command line parameters as list of strings
-          (for example  ``["--help"]``).
-
-    Returns:
-      :obj:`argparse.Namespace`: command line parameters namespace
-    """
-    parser = argparse.ArgumentParser(description="Just a Fibonacci demonstration")
-    parser.add_argument(
-        "--version",
-        action="version",
-        version="rfidacd {ver}".format(ver=__version__),
-    )
-    parser.add_argument(dest="n", help="n-th Fibonacci number", type=int, metavar="INT")
-    parser.add_argument(
-        "-v",
-        "--verbose",
-        dest="loglevel",
-        help="set loglevel to INFO",
-        action="store_const",
-        const=logging.INFO,
-    )
-    parser.add_argument(
-        "-vv",
-        "--very-verbose",
-        dest="loglevel",
-        help="set loglevel to DEBUG",
-        action="store_const",
-        const=logging.DEBUG,
-    )
-    return parser.parse_args(args)
-
-
-def setup_logging(loglevel):
-    """Setup basic logging
-
-    Args:
-      loglevel (int): minimum loglevel for emitting messages
-    """
-    logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
-    logging.basicConfig(
-        level=loglevel, stream=sys.stdout, format=logformat, datefmt="%Y-%m-%d %H:%M:%S"
-    )
-
-
-def main(args):
-    """Wrapper allowing :func:`fib` to be called with string arguments in a CLI fashion
-
-    Instead of returning the value from :func:`fib`, it prints the result to the
-    ``stdout`` in a nicely formated message.
-
-    Args:
-      args (List[str]): command line parameters as list of strings
-          (for example  ``["--verbose", "42"]``).
-    """
-    args = parse_args(args)
-    setup_logging(args.loglevel)
-    _logger.debug("Starting crazy calculations...")
-    print("The {}-th Fibonacci number is {}".format(args.n, fib(args.n)))
-    _logger.info("Script ends here")
-
-
-def run():
-    """Calls :func:`main` passing the CLI arguments extracted from :obj:`sys.argv`
-
-    This function can be used as entry point to create console scripts with setuptools.
-    """
-    main(sys.argv[1:])
-
-
-if __name__ == "__main__":
-    # ^  This is a guard statement that will prevent the following code from
-    #    being executed in the case someone imports this file instead of
-    #    executing it as a script.
-    #    https://docs.python.org/3/library/__main__.html
-
-    # After installing your project with pip, users can also run your Python
-    # modules as scripts via the ``-m`` flag, as defined in PEP 338::
-    #
-    #     python -m rfidacd.skeleton 42
-    #
-    run()