From: Philipp Klaus Date: Tue, 30 May 2017 16:51:31 +0000 (+0200) Subject: EPICS -> MQTT Bridge added X-Git-Url: https://jspc29.x-matter.uni-frankfurt.de/git/?a=commitdiff_plain;h=587b98b62289c54813edcf75b546088f83d1c526;p=mvd_epics.git EPICS -> MQTT Bridge added --- diff --git a/epics_mqtt_bridge/epics_mqtt_bridge/__init__.py b/epics_mqtt_bridge/epics_mqtt_bridge/__init__.py new file mode 100644 index 0000000..fb06b89 --- /dev/null +++ b/epics_mqtt_bridge/epics_mqtt_bridge/__init__.py @@ -0,0 +1,79 @@ +import epics +import paho.mqtt.publish as publish + +import os +import logging + +logger = logging.getLogger(__name__) + +class EPICS_MQTT_Bridge(): + def __init__(self, config): + self.config = config + + self.EPICS_CA_ADDR_LIST = config.get('EPICS', 'EPICS_CA_ADDR_LIST', fallback=None) + self.PYEPICS_LIBCA = config.get('EPICS', 'PYEPICS_LIBCA', fallback=None) + self.EPICS_BASE = config.get('EPICS', 'EPICS_BASE', fallback=None) + self.EPICS_HOST_ARCH = config.get('EPICS', 'EPICS_HOST_ARCH', fallback=None) + + if self.EPICS_CA_ADDR_LIST: os.putenv('EPICS_CA_ADDR_LIST', self.EPICS_CA_ADDR_LIST) + if self.PYEPICS_LIBCA: os.putenv('PYEPICS_LIBCA', self.PYEPICS_LIBCA) + if self.EPICS_BASE: os.putenv('EPICS_BASE', self.EPICS_BASE) + if self.EPICS_HOST_ARCH: os.putenv('EPICS_HOST_ARCH', self.EPICS_HOST_ARCH) + + self.MQTT_hostname = config.get('MQTT', 'Hostname') + + self.DEBUG = False + + logger.info("Using the following EPICS LIBCA: %s", epics.ca.find_libca()) + + def check_once(self): + msgs = [] + for pvname in self.config['PVs']: + logger.info("Query the following PV: %s", pvname) + pv = epics.pv.get_pv(pvname) + char_val = pv.get(as_string=True, use_monitor=False) + logger.info("Current Value: %s = %s", pvname, char_val) + msgs.append({'topic': pvname.replace(':', '/'), 'payload': char_val}) + publish.multiple(msgs, hostname=self.MQTT_hostname) + + def epics_connection_callback(self, pvname, conn, **kwarg): + """ + Callback function for PV changes + http://cars9.uchicago.edu/software/python/pyepics3/pv.html#pv-callbacks-label + """ + publish.single(pvname+'.CONNECTED', int(conn), hostname=self.MQTT_hostname) + + def epics_callback(self, pvname, value, char_value, precision, **kwarg): + """ + Callback function for PV changes + http://cars9.uchicago.edu/software/python/pyepics3/pv.html#pv-callbacks-label + """ + if precision: + out_val = ('{:.' + str(precision) + 'f}').format(value) + else: + out_val = char_value + logger.debug("PV Change: %s = %s", pvname, out_val) + publish.single(pvname, out_val, hostname=self.MQTT_hostname) + + def monitor(self): + """ + Start monitoring all PVs + Alternative implementation: + http://cars9.uchicago.edu/software/python/pyepics3/pv.html#example-of-connection-callback + """ + for pvname in self.config['PVs']: + logger.info("Starting to monitor the following PV: %s", pvname) + pv = epics.pv.get_pv(pvname, form='native') + pv.connection_callbacks = [self.epics_connection_callback] + pv.get(use_monitor=False) # query once + pv.add_callback(callback=self.epics_callback, with_ctrlvars=True, run_now=True) + + def monitor_clear(self): + """ + Clear the monitor of all PVs. + """ + for pvname in self.config['PVs']: + logger.info("Clear monitor of the following PV: %s", pvname) + pv = epics.get_pv(pvname, form='native') + pv.disconnect() + diff --git a/epics_mqtt_bridge/epics_mqtt_bridge/cli.py b/epics_mqtt_bridge/epics_mqtt_bridge/cli.py new file mode 100644 index 0000000..939ec1c --- /dev/null +++ b/epics_mqtt_bridge/epics_mqtt_bridge/cli.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +from . import EPICS_MQTT_Bridge + +import argparse +import configparser +import sys +import logging +import time + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--configuration', '-c', help='configuration file') + parser.add_argument('--debug', action='store_true', help='enable debugging mode') + args = parser.parse_args() + if args.debug: logging.basicConfig(level='DEBUG') + config = configparser.ConfigParser(delimiters=('=',)) + config.optionxform = lambda option: option + config.read(args.configuration) + if any(s not in config.sections() for s in ('MQTT', 'PVs', 'EPICS')): + sys.stderr.write("Not a suitable config file: Sections EPICS, MQTT, PVs required.\n") + sys.exit(2) + emb = EPICS_MQTT_Bridge(config) + #emb.check_once() + emb.monitor() + try: + while True: + time.sleep(0.1) + except KeyboardInterrupt: + emb.monitor_clear() + +if __name__ == "__main__": main() diff --git a/epics_mqtt_bridge/setup.py b/epics_mqtt_bridge/setup.py new file mode 100644 index 0000000..985c703 --- /dev/null +++ b/epics_mqtt_bridge/setup.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +setup(name='epics_mqtt_bridge', + version = '0.1.dev0', + description = 'Python package to bridge EPICS PVs to MQTT topics', + long_description = '', + author = 'Philipp Klaus', + author_email = 'klaus@physik.uni-frankfurt.de', + url = 'https://github.com/pklaus/epics_mqtt_bridge', + license = 'GPL', + packages = ['epics_mqtt_bridge',], + entry_points = { + 'console_scripts': [ + 'epics_mqtt_bridge = epics_mqtt_bridge.cli:main', + ], + }, + include_package_data = False, + zip_safe = True, + platforms = 'any', + install_requires = [ + "pyepics", + "paho-mqtt", + ], + keywords = 'EPICS MQTT Bridge', + classifiers = [ + 'Development Status :: 4 - Beta', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Topic :: Scientific/Engineering :: Visualization', + 'Topic :: System :: Hardware :: Hardware Drivers', + ] +) +