From a1c70f47ea068d62b98dd046a723982f12b33436 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Sat, 28 Oct 2023 14:28:08 +0000 Subject: [PATCH 01/13] syncbox agent --- socs/agents/meinberg_syncbox/__init__.py | 0 socs/agents/meinberg_syncbox/agent.py | 392 +++++++++++++++++++++++ socs/agents/ocs_plugin_so.py | 1 + socs/mibs/MBG-SYNCBOX-N2X-MIB.py | 119 +++++++ 4 files changed, 512 insertions(+) create mode 100644 socs/agents/meinberg_syncbox/__init__.py create mode 100644 socs/agents/meinberg_syncbox/agent.py create mode 100644 socs/mibs/MBG-SYNCBOX-N2X-MIB.py diff --git a/socs/agents/meinberg_syncbox/__init__.py b/socs/agents/meinberg_syncbox/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py new file mode 100644 index 000000000..83e3e2572 --- /dev/null +++ b/socs/agents/meinberg_syncbox/agent.py @@ -0,0 +1,392 @@ +import argparse +import os +import time + +import txaio +from autobahn.twisted.util import sleep as dsleep +from ocs import ocs_agent, site_config +from ocs.ocs_twisted import TimeoutLock +from twisted.internet.defer import inlineCallbacks + +import socs +from socs.snmp import SNMPTwister + +# For logging +txaio.use_twisted() + + +def _extract_oid_field_and_value(get_result): + """Extract field names and OID values from SNMP GET results. + + The ObjectType objects returned from pysnmp interactions contain the + info we want to use for field names, specifically the OID and associated + integer for uniquely identifying duplicate OIDs, as well as the value of the + OID, which we want to save. + + Here we use the prettyPrint() method to get the OID name, requiring + some string manipulation. We also just grab the hidden ._value + directly, as this seemed the cleanest way to get an actual value of a + normal type. Without doing this we get a pysnmp defined Integer32 or + DisplayString, which were awkward to handle, particularly the + DisplayString. + + Parameters + ---------- + get_result : pysnmp.smi.rfc1902.ObjectType + Result from a pysnmp GET command. + + Returns + ------- + field_name : str + Field name for an OID, i.e. 'outletStatus_1' + oid_value : int or str + Associated value for the OID. Returns None if not an int or str + oid_description : str + String description of the OID value. + """ + # OID from SNMP GET + oid = get_result[0].prettyPrint() + # Makes something like 'IBOOTPDU-MIB::outletStatus.1' + # look like 'outletStatus_1' + field_name = oid.split("::")[1].replace('.', '_') + + # Grab OID value, mostly these are integers + oid_value = get_result[1]._value + oid_description = get_result[1].prettyPrint() + + # Decode string values + if isinstance(oid_value, bytes): + oid_value = oid_value.decode("utf-8") + + # I don't expect any other types at the moment, but just in case. + if not isinstance(oid_value, (int, bytes, str)): + oid_value = None + + return field_name, oid_value, oid_description + + +def _build_message(get_result, time, blockname): + """Build the message for publication on an OCS Feed. + + Parameters + ---------- + get_result : pysnmp.smi.rfc1902.ObjectType + Result from a pysnmp GET command. + names : list + List of strings for outlet names + time : float + Timestamp for when the SNMP GET was issued. + + Returns + ------- + message : dict + OCS Feed formatted message for publishing + """ + message = { + 'block_name': blockname, + 'timestamp': time, + 'data': {} + } + + for item in get_result: + field_name, oid_value, oid_description = _extract_oid_field_and_value(item) + + if oid_value is None: + continue + + message['data'][field_name] = oid_value + message['data'][field_name + "_description"] = oid_description + + return message + + +def update_cache(get_result, timestamp): + """Update the OID Value Cache. + + The OID Value Cache is used to store each unique OID and will be passed to + session.data + + The cache consists of a dictionary, with the unique OIDs as keys, and + another dictionary as the value. Each of these nested dictionaries contains + the OID values, name, and description (decoded string). An example for a + single OID, with connection status and timestamp information:: + + {"outletStatus_0": {"status": 1, + "name": Outlet-1, + "description": "on"}, + "syncbox_connection": {"last_attempt": 1598543359.6326838, + "connected": True}, + "timestamp": 1656085022.680916} + + Parameters + ---------- + get_result : pysnmp.smi.rfc1902.ObjectType + Result from a pysnmp GET command. + timestamp : float + Timestamp for when the SNMP GET was issued. + """ + oid_cache = {} + # Return disconnected if SNMP response is empty + if get_result is None: + oid_cache['syncbox_connection'] = {'last_attempt': time.time(), + 'connected': False} + return oid_cache + + for item in get_result: + field_name, oid_value, oid_description = _extract_oid_field_and_value(item) + if oid_value is None: + continue + + # Update OID Cache for session.data + oid_cache[field_name] = {"status": oid_value} + oid_cache[field_name]["description"] = oid_description + oid_cache['syncbox_connection'] = {'last_attempt': time.time(), + 'connected': True} + oid_cache['timestamp'] = timestamp + + return oid_cache + + +class MeinbergSyncboxAgent: + """Monitor the syncbox system via SNMP. + + Parameters + ---------- + agent : OCSAgent + OCSAgent object which forms this Agent + address : str + Address of the syncbox. + port : int + SNMP port to issue GETs to, default to 161. + version : int + SNMP version for communication (1, 2, or 3), defaults to 1. + + Attributes + ---------- + agent : OCSAgent + OCSAgent object which forms this Agent + is_streaming : bool + Tracks whether or not the agent is actively issuing SNMP GET commands + to the syncbox. Setting to false stops sending commands. + log : txaio.tx.Logger + txaio logger object, created by the OCSAgent + """ + + def __init__(self, agent, address, port=161, version=1): + self.agent = agent + self.is_streaming = False + self.log = self.agent.log + self.lock = TimeoutLock() + + self.log.info(f'Using SNMP version {version}.') + self.version = version + self.address = address + self.snmp = SNMPTwister(address, port) + self.connected = True + + self.lastGet = 0 + self.sample_period = 60 + + agg_params = { + 'frame_length': 10 * 60 # [sec] + } + self.agent.register_feed('syncbox', + record=True, + agg_params=agg_params, + buffer_time=0) + + @ocs_agent.param('test_mode', default=False, type=bool) + @inlineCallbacks + def acq(self, session, params=None): + """acq() + + **Process** - Fetch values from the syncbox via SNMP. + + Notes + ----- + The most recent data collected is stored in session.data in the + structure:: + + >>> response.session['data'] + {'outletStatus_0': + {'status': 1, + 'name': 'Outlet-1', + 'description': 'on'}, + 'outletStatus_1': + {'status': 0, + 'name': 'Outlet-2', + 'description': 'off'}, + ... + 'syncbox_connection': + {'last_attempt': 1656085022.680916, + 'connected': True}, + 'timestamp': 1656085022.680916, + 'address': '10.10.10.50'} + """ + + session.set_status('running') + self.is_streaming = True + while self.is_streaming: + yield dsleep(1) + if not self.connected: + self.log.error('No SNMP response. Check your connection!') + self.log.info('Trying to reconnect.') + + read_time = time.time() + + # Check if sample period has passed before getting status + if (read_time - self.lastGet) < self.sample_period: + continue + + main_get_list = [] + get_list = [] + + # Create the list of OIDs to send get commands + oids = ['mbgSyncboxN2XSerialNumber', + 'mbgSyncboxN2XFirmwareRevision', + 'mbgSyncboxN2XSystemTime', + 'mbgSyncboxN2XCurrentRefSource', + 'mbgSyncboxN2XPtpProfile', + 'mbgSyncboxN2XPtpNwProt', + 'mbgSyncboxN2XPtpPortState', + 'mbgSyncboxN2XPtpDelayMechanism', + 'mbgSyncboxN2XPtpDelayRequestInterval', + 'mbgSyncboxN2XPtpTimescale', + 'mbgSyncboxN2XPtpUTCOffset', + 'mbgSyncboxN2XPtpLeapSecondAnnounced', + 'mbgSyncboxN2XPtpGrandmasterClockID', + 'mbgSyncboxN2XPtpGrandmasterTimesource', + 'mbgSyncboxN2XPtpGrandmasterPriority1', + 'mbgSyncboxN2XPtpGrandmasterClockClass', + 'mbgSyncboxN2XPtpGrandmasterClockAccuracy', + 'mbgSyncboxN2XPtpGrandmasterClockVariance', + 'mbgSyncboxN2XPtpOffsetToGrandmaster', + 'mbgSyncboxN2XPtpMeanPathDelay'] + + for oid in oids: + main_get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, 0)) + get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, 0)) + + general_get_result = yield self.snmp.get(get_list, self.version) + if general_get_result is None: + self.connected = False + continue + self.connected = True + + output_oids = ['mbgSyncboxN2XOutputMode'] + + output_get_results = [] + outputs = 2 + for i in range(outputs): + get_list = [] + for oid in output_oids: + main_get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, i + 1)) + get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, i + 1)) + output_get_result = yield self.snmp.get(get_list, self.version) + output_get_results.append(output_get_result) + + get_results = [] + # Issue SNMP GET command + for get in main_get_list: + get_result = yield self.snmp.get([get], self.version) + if get_result is None: + self.connected = False + continue + self.connected = True + get_results.append(get_result[0]) + + # Do not publish if syncbox connection has dropped + try: + # Update session.data + session.data = update_cache(get_results, read_time) + # oid_cache['address'] = self.address + # session.data = oid_cache + self.log.debug("{data}", data=session.data) + + if not self.connected: + raise ConnectionError('No SNMP response. Check your connection.') + + self.lastGet = time.time() + # Publish to feed + if general_get_result is not None: + message = _build_message(general_get_result, read_time, 'syncbox') + self.log.debug("{msg}", msg=message) + session.app.publish_to_feed('syncbox', message) + for i, result in enumerate(output_get_results): + if result is not None: + blockname = f'output_{i}' + message = _build_message(result, read_time, blockname) + self.log.debug("{msg}", msg=message) + session.app.publish_to_feed('syncbox', message) + except ConnectionError as e: + self.log.error(f'{e}') + yield dsleep(1) + self.log.info('Trying to reconnect.') + + if params['test_mode']: + break + + return True, "Finished Recording" + + def _stop_acq(self, session, params=None): + """_stop_acq() + **Task** - Stop task associated with acq process. + """ + if self.is_streaming: + session.set_status('stopping') + self.is_streaming = False + return True, "Stopping Recording" + else: + return False, "Acq is not currently running" + + +def add_agent_args(parser=None): + """ + Build the argument parser for the Agent. Allows sphinx to automatically + build documentation based on this function. + """ + if parser is None: + parser = argparse.ArgumentParser() + + pgroup = parser.add_argument_group("Agent Options") + pgroup.add_argument("--address", help="Address to listen to.") + pgroup.add_argument("--port", default=161, + help="Port to listen on.") + pgroup.add_argument("--snmp-version", default='1', choices=['1', '2', '3'], + help="SNMP version for communication. Must match " + + "configuration on the syncbox.") + pgroup.add_argument("--mode", default='acq', choices=['acq', 'test']) + + return parser + + +def main(args=None): + # Start logging + txaio.start_logging(level=os.environ.get("LOGLEVEL", "info")) + + parser = add_agent_args() + args = site_config.parse_args(agent_class='MeinbergSyncboxAgent', + parser=parser, + args=args) + + if args.mode == 'acq': + init_params = True + elif args.mode == 'test': + init_params = False + + agent, runner = ocs_agent.init_site_agent(args) + p = MeinbergSyncboxAgent(agent, + address=args.address, + port=int(args.port), + version=int(args.snmp_version)) + + agent.register_process("acq", + p.acq, + p._stop_acq, + startup=init_params, blocking=False) + + runner.run(agent, auto_reconnect=True) + + +if __name__ == "__main__": + main() diff --git a/socs/agents/ocs_plugin_so.py b/socs/agents/ocs_plugin_so.py index f23a1fd31..d6b2513f5 100644 --- a/socs/agents/ocs_plugin_so.py +++ b/socs/agents/ocs_plugin_so.py @@ -32,6 +32,7 @@ ('LATRtXYStageAgent', 'xy_stage/agent.py'), ('MagpieAgent', 'magpie/agent.py'), ('MeinbergM1000Agent', 'meinberg_m1000/agent.py'), + ('MeinbergSyncboxAgent', 'meinberg_syncbox/agent.py'), ('PfeifferAgent', 'pfeiffer_tpg366/agent.py'), ('PfeifferTC400Agent', 'pfeiffer_tc400/agent.py'), ('PysmurfController', 'pysmurf_controller/agent.py'), diff --git a/socs/mibs/MBG-SYNCBOX-N2X-MIB.py b/socs/mibs/MBG-SYNCBOX-N2X-MIB.py new file mode 100644 index 000000000..d10dcefe9 --- /dev/null +++ b/socs/mibs/MBG-SYNCBOX-N2X-MIB.py @@ -0,0 +1,119 @@ +# +# PySNMP MIB module MBG-SYNCBOX-N2X-MIB (https://www.pysnmp.com/pysmi) +# ASN.1 source file://./MBG-SYNCBOX-N2X-MIB.mib +# Produced by pysmi-1.1.13 at Fri Oct 27 11:50:07 2023 +# On host HAWKING platform Linux version 5.15.90.1-microsoft-standard-WSL2 by user davidvng +# Using Python version 3.8.8 (default, Apr 13 2021, 19:58:26) +# +Integer, ObjectIdentifier, OctetString = mibBuilder.importSymbols("ASN1", "Integer", "ObjectIdentifier", "OctetString") +NamedValues, = mibBuilder.importSymbols("ASN1-ENUMERATION", "NamedValues") +ValueSizeConstraint, ConstraintsIntersection, ValueRangeConstraint, SingleValueConstraint, ConstraintsUnion = mibBuilder.importSymbols("ASN1-REFINEMENT", "ValueSizeConstraint", "ConstraintsIntersection", "ValueRangeConstraint", "SingleValueConstraint", "ConstraintsUnion") +mbgSnmpRoot, = mibBuilder.importSymbols("MBG-SNMP-ROOT-MIB", "mbgSnmpRoot") +NotificationGroup, ModuleCompliance, ObjectGroup = mibBuilder.importSymbols("SNMPv2-CONF", "NotificationGroup", "ModuleCompliance", "ObjectGroup") +NotificationType, Integer32, MibScalar, MibTable, MibTableRow, MibTableColumn, TimeTicks, ObjectIdentity, iso, ModuleIdentity, Counter64, Unsigned32, Bits, Gauge32, Counter32, IpAddress, MibIdentifier = mibBuilder.importSymbols("SNMPv2-SMI", "NotificationType", "Integer32", "MibScalar", "MibTable", "MibTableRow", "MibTableColumn", "TimeTicks", "ObjectIdentity", "iso", "ModuleIdentity", "Counter64", "Unsigned32", "Bits", "Gauge32", "Counter32", "IpAddress", "MibIdentifier") +TextualConvention, DisplayString = mibBuilder.importSymbols("SNMPv2-TC", "TextualConvention", "DisplayString") +mbgSyncboxN2X = ModuleIdentity((1, 3, 6, 1, 4, 1, 5597, 40)) +mbgSyncboxN2X.setRevisions(('2013-09-03 00:00',)) +if mibBuilder.loadTexts: mbgSyncboxN2X.setLastUpdated('201309030000Z') +if mibBuilder.loadTexts: mbgSyncboxN2X.setOrganization('www.meinberg.de') +mbgSyncboxN2XGeneral = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 0)) +mbgSyncboxN2XSerialNumber = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 1), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XSerialNumber.setStatus('current') +mbgSyncboxN2XFirmwareRevision = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 2), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XFirmwareRevision.setStatus('current') +mbgSyncboxN2XSystemTime = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 3), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XSystemTime.setStatus('current') +mbgSyncboxN2XCurrentRefSource = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 4), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XCurrentRefSource.setStatus('current') +mbgSyncboxN2XNetworkTimeProtocol = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 1)) +mbgSyncboxN2XNtpSyncStatus = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 1, 1), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpSyncStatus.setStatus('current') +mbgSyncboxN2XNtpSystemPeer = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 1, 2), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpSystemPeer.setStatus('current') +mbgSyncboxN2XNtpStratum = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 1, 3), Unsigned32()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpStratum.setStatus('current') +mbgSyncboxN2XNtpRefSourceTable = MibTable((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4), ) +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceTable.setStatus('current') +mbgSyncboxN2XNtpRefSourceTableEntry = MibTableRow((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1), ).setIndexNames((0, "MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceIndex")) +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceTableEntry.setStatus('current') +mbgSyncboxN2XNtpRefSourceIndex = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 1), Unsigned32().subtype(subtypeSpec=ValueRangeConstraint(0, 6))) +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceIndex.setStatus('current') +mbgSyncboxN2XNtpRefSourceHostname = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 2), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceHostname.setStatus('current') +mbgSyncboxN2XNtpRefSourceStratum = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 3), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceStratum.setStatus('current') +mbgSyncboxN2XNtpRefSourceReferenceID = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 4), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceReferenceID.setStatus('current') +mbgSyncboxN2XNtpRefSourceReach = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 5), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceReach.setStatus('current') +mbgSyncboxN2XNtpRefSourceCurrPoll = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 6), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceCurrPoll.setStatus('current') +mbgSyncboxN2XNtpRefSourceMinPoll = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 7), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceMinPoll.setStatus('current') +mbgSyncboxN2XNtpRefSourceMaxPoll = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 8), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceMaxPoll.setStatus('current') +mbgSyncboxN2XNtpRefSourceConfigOptions = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 9), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceConfigOptions.setStatus('current') +mbgSyncboxN2XNtpRefSourcePathDelay = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 10), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourcePathDelay.setStatus('current') +mbgSyncboxN2XNtpRefSourceOffset = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 11), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceOffset.setStatus('current') +mbgSyncboxN2XNtpRefSourceJitter = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 12), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceJitter.setStatus('current') +mbgSyncboxN2XPrecisionTimeProtocol = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 2)) +mbgSyncboxN2XPtpProfile = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 1), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2))).clone(namedValues=NamedValues(("none", 0), ("power", 1), ("telecom", 2)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpProfile.setStatus('current') +mbgSyncboxN2XPtpNwProt = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 2), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2, 3, 4, 5, 6))).clone(namedValues=NamedValues(("unknown", 0), ("ipv4", 1), ("ipv6", 2), ("ieee802-3", 3), ("deviceNet", 4), ("controlNet", 5), ("profiNet", 6)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpNwProt.setStatus('current') +mbgSyncboxN2XPtpPortState = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 3), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))).clone(namedValues=NamedValues(("uninitialized", 0), ("initializing", 1), ("faulty", 2), ("disabled", 3), ("listening", 4), ("preMaster", 5), ("master", 6), ("passive", 7), ("uncalibrated", 8), ("slave", 9)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpPortState.setStatus('current') +mbgSyncboxN2XPtpDelayMechanism = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 4), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1))).clone(namedValues=NamedValues(("e2e", 0), ("p2p", 1)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpDelayMechanism.setStatus('current') +mbgSyncboxN2XPtpDelayRequestInterval = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 5), Integer32()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpDelayRequestInterval.setStatus('current') +mbgSyncboxN2XPtpTimescale = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 6), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1))).clone(namedValues=NamedValues(("tai", 0), ("arb", 1)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpTimescale.setStatus('current') +mbgSyncboxN2XPtpUTCOffset = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 7), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpUTCOffset.setStatus('current') +mbgSyncboxN2XPtpLeapSecondAnnounced = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 8), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpLeapSecondAnnounced.setStatus('current') +mbgSyncboxN2XPtpGrandmasterClockID = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 9), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockID.setStatus('current') +mbgSyncboxN2XPtpGrandmasterTimesource = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 10), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(16, 32, 48, 64, 80, 96, 144, 160))).clone(namedValues=NamedValues(("atomicClock", 16), ("gps", 32), ("terrestrialRadio", 48), ("ptp", 64), ("ntp", 80), ("handSet", 96), ("other", 144), ("internalOscillator", 160)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterTimesource.setStatus('current') +mbgSyncboxN2XPtpGrandmasterPriority1 = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 11), Unsigned32()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterPriority1.setStatus('current') +mbgSyncboxN2XPtpGrandmasterClockClass = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 12), Unsigned32()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockClass.setStatus('current') +mbgSyncboxN2XPtpGrandmasterClockAccuracy = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 13), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49))).clone(namedValues=NamedValues(("accurateToWithin25ns", 32), ("accurateToWithin100ns", 33), ("accurateToWithin250ns", 34), ("accurateToWithin1us", 35), ("accurateToWithin2Point5us", 36), ("accurateToWithin10us", 37), ("accurateToWithin25us", 38), ("accurateToWithin100us", 39), ("accurateToWithin250us", 40), ("accurateToWithin1ms", 41), ("accurateToWithin2Point5ms", 42), ("accurateToWithin10ms", 43), ("accurateToWithin25ms", 44), ("accurateToWithin100ms", 45), ("accurateToWithin250ms", 46), ("accurateToWithin1s", 47), ("accurateToWithin10s", 48), ("accurateToGreaterThan10s", 49)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockAccuracy.setStatus('current') +mbgSyncboxN2XPtpGrandmasterClockVariance = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 14), Unsigned32()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockVariance.setStatus('current') +mbgSyncboxN2XPtpGrandmasterPriority2 = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 15), Unsigned32()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterPriority2.setStatus('current') +mbgSyncboxN2XPtpOffsetToGrandmaster = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 16), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpOffsetToGrandmaster.setStatus('current') +mbgSyncboxN2XPtpMeanPathDelay = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 17), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XPtpMeanPathDelay.setStatus('current') +mbgSyncboxN2XOutputs = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 3)) +mbgSyncboxN2XOutputsTable = MibTable((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1), ) +if mibBuilder.loadTexts: mbgSyncboxN2XOutputsTable.setStatus('current') +mbgSyncboxN2XOutputsTableEntry = MibTableRow((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1, 1), ).setIndexNames((0, "MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XOutputIndex")) +if mibBuilder.loadTexts: mbgSyncboxN2XOutputsTableEntry.setStatus('current') +mbgSyncboxN2XOutputIndex = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1, 1, 1), Unsigned32().subtype(subtypeSpec=ValueRangeConstraint(0, 2))) +if mibBuilder.loadTexts: mbgSyncboxN2XOutputIndex.setStatus('current') +mbgSyncboxN2XOutputMode = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1, 1, 2), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))).clone(namedValues=NamedValues(("idle", 0), ("timer", 1), ("singleShot", 2), ("cyclicPulse", 3), ("pulsePerSecond", 4), ("pulsePerMinute", 5), ("pulsePerHour", 6), ("emulatedDCF77", 7), ("positionOK", 8), ("timeSync", 9), ("allSync", 10), ("timecode", 11), ("timestring", 12), ("tenMHz", 13), ("emulatedDCF77M59", 14), ("synthesizer", 15), ("timeSlots", 16)))).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XOutputMode.setStatus('current') +mbgSyncboxN2XSerialString = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 3, 2), DisplayString()).setMaxAccess("readonly") +if mibBuilder.loadTexts: mbgSyncboxN2XSerialString.setStatus('current') +mbgSyncboxN2XConformance = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 10)) +mbgSyncboxN2XCompliances = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 10, 0)) +mbgSyncboxN2XGroups = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 10, 1)) +mbgSyncboxN2XCompliance = ModuleCompliance((1, 3, 6, 1, 4, 1, 5597, 40, 10, 0, 0)).setObjects(("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XObjectsGroup")) + +if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0): + mbgSyncboxN2XCompliance = mbgSyncboxN2XCompliance.setStatus('current') +mbgSyncboxN2XObjectsGroup = ObjectGroup((1, 3, 6, 1, 4, 1, 5597, 40, 10, 1, 0)).setObjects(("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XSerialNumber"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XFirmwareRevision"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XSystemTime"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XCurrentRefSource"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpSyncStatus"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpSystemPeer"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpStratum"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceHostname"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceStratum"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceReferenceID"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceReach"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceCurrPoll"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceMinPoll"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceMaxPoll"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceConfigOptions"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourcePathDelay"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceOffset"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceJitter"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpProfile"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpNwProt"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpPortState"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpDelayMechanism"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpDelayRequestInterval"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpTimescale"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpUTCOffset"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpLeapSecondAnnounced"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpGrandmasterClockID"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpGrandmasterTimesource"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpGrandmasterPriority1"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpGrandmasterClockClass"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpGrandmasterClockAccuracy"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpGrandmasterClockVariance"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpGrandmasterPriority2"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpOffsetToGrandmaster"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XPtpMeanPathDelay"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XOutputMode"), ("MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XSerialString")) +if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0): + mbgSyncboxN2XObjectsGroup = mbgSyncboxN2XObjectsGroup.setStatus('current') +mibBuilder.exportSymbols("MBG-SYNCBOX-N2X-MIB", mbgSyncboxN2XPtpPortState=mbgSyncboxN2XPtpPortState, mbgSyncboxN2XPtpGrandmasterClockAccuracy=mbgSyncboxN2XPtpGrandmasterClockAccuracy, mbgSyncboxN2XCompliances=mbgSyncboxN2XCompliances, mbgSyncboxN2XNtpSyncStatus=mbgSyncboxN2XNtpSyncStatus, mbgSyncboxN2XNtpRefSourceTable=mbgSyncboxN2XNtpRefSourceTable, mbgSyncboxN2XPtpGrandmasterClockVariance=mbgSyncboxN2XPtpGrandmasterClockVariance, mbgSyncboxN2XNtpRefSourceCurrPoll=mbgSyncboxN2XNtpRefSourceCurrPoll, mbgSyncboxN2XNtpRefSourceReach=mbgSyncboxN2XNtpRefSourceReach, mbgSyncboxN2XNtpRefSourceJitter=mbgSyncboxN2XNtpRefSourceJitter, mbgSyncboxN2XSerialString=mbgSyncboxN2XSerialString, mbgSyncboxN2XNtpRefSourceMinPoll=mbgSyncboxN2XNtpRefSourceMinPoll, PYSNMP_MODULE_ID=mbgSyncboxN2X, mbgSyncboxN2XNtpRefSourceReferenceID=mbgSyncboxN2XNtpRefSourceReferenceID, mbgSyncboxN2XNtpRefSourceTableEntry=mbgSyncboxN2XNtpRefSourceTableEntry, mbgSyncboxN2XPtpOffsetToGrandmaster=mbgSyncboxN2XPtpOffsetToGrandmaster, mbgSyncboxN2XSystemTime=mbgSyncboxN2XSystemTime, mbgSyncboxN2XNtpRefSourceStratum=mbgSyncboxN2XNtpRefSourceStratum, mbgSyncboxN2XOutputsTableEntry=mbgSyncboxN2XOutputsTableEntry, mbgSyncboxN2XOutputMode=mbgSyncboxN2XOutputMode, mbgSyncboxN2XOutputIndex=mbgSyncboxN2XOutputIndex, mbgSyncboxN2XObjectsGroup=mbgSyncboxN2XObjectsGroup, mbgSyncboxN2XNtpRefSourceIndex=mbgSyncboxN2XNtpRefSourceIndex, mbgSyncboxN2XFirmwareRevision=mbgSyncboxN2XFirmwareRevision, mbgSyncboxN2XPtpGrandmasterPriority2=mbgSyncboxN2XPtpGrandmasterPriority2, mbgSyncboxN2XPtpGrandmasterClockClass=mbgSyncboxN2XPtpGrandmasterClockClass, mbgSyncboxN2XNtpRefSourceHostname=mbgSyncboxN2XNtpRefSourceHostname, mbgSyncboxN2XPrecisionTimeProtocol=mbgSyncboxN2XPrecisionTimeProtocol, mbgSyncboxN2XNtpSystemPeer=mbgSyncboxN2XNtpSystemPeer, mbgSyncboxN2X=mbgSyncboxN2X, mbgSyncboxN2XPtpMeanPathDelay=mbgSyncboxN2XPtpMeanPathDelay, mbgSyncboxN2XGroups=mbgSyncboxN2XGroups, mbgSyncboxN2XConformance=mbgSyncboxN2XConformance, mbgSyncboxN2XNtpRefSourceConfigOptions=mbgSyncboxN2XNtpRefSourceConfigOptions, mbgSyncboxN2XNtpRefSourceMaxPoll=mbgSyncboxN2XNtpRefSourceMaxPoll, mbgSyncboxN2XPtpGrandmasterClockID=mbgSyncboxN2XPtpGrandmasterClockID, mbgSyncboxN2XOutputs=mbgSyncboxN2XOutputs, mbgSyncboxN2XPtpDelayRequestInterval=mbgSyncboxN2XPtpDelayRequestInterval, mbgSyncboxN2XNtpRefSourcePathDelay=mbgSyncboxN2XNtpRefSourcePathDelay, mbgSyncboxN2XGeneral=mbgSyncboxN2XGeneral, mbgSyncboxN2XNtpRefSourceOffset=mbgSyncboxN2XNtpRefSourceOffset, mbgSyncboxN2XPtpGrandmasterTimesource=mbgSyncboxN2XPtpGrandmasterTimesource, mbgSyncboxN2XPtpGrandmasterPriority1=mbgSyncboxN2XPtpGrandmasterPriority1, mbgSyncboxN2XPtpDelayMechanism=mbgSyncboxN2XPtpDelayMechanism, mbgSyncboxN2XNetworkTimeProtocol=mbgSyncboxN2XNetworkTimeProtocol, mbgSyncboxN2XSerialNumber=mbgSyncboxN2XSerialNumber, mbgSyncboxN2XCompliance=mbgSyncboxN2XCompliance, mbgSyncboxN2XOutputsTable=mbgSyncboxN2XOutputsTable, mbgSyncboxN2XPtpNwProt=mbgSyncboxN2XPtpNwProt, mbgSyncboxN2XPtpUTCOffset=mbgSyncboxN2XPtpUTCOffset, mbgSyncboxN2XNtpStratum=mbgSyncboxN2XNtpStratum, mbgSyncboxN2XPtpLeapSecondAnnounced=mbgSyncboxN2XPtpLeapSecondAnnounced, mbgSyncboxN2XPtpTimescale=mbgSyncboxN2XPtpTimescale, mbgSyncboxN2XPtpProfile=mbgSyncboxN2XPtpProfile, mbgSyncboxN2XCurrentRefSource=mbgSyncboxN2XCurrentRefSource) From c133ec5dc312cdc99515d5c8f83daddad8e7e7f3 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Sat, 28 Oct 2023 14:45:49 +0000 Subject: [PATCH 02/13] add to plugin --- socs/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/socs/plugin.py b/socs/plugin.py index d337f7d91..426f2777b 100644 --- a/socs/plugin.py +++ b/socs/plugin.py @@ -24,6 +24,7 @@ 'LATRtXYStageAgent': {'module': 'socs.agents.xy_stage.agent', 'entry_point': 'main'}, 'MagpieAgent': {'module': 'socs.agents.magpie.agent', 'entry_point': 'main'}, 'MeinbergM1000Agent': {'module': 'socs.agents.meinberg_m1000.agent', 'entry_point': 'main'}, + 'MeinbergSyncboxAgent': {'module': 'socs.agents.meinberg_syncbox.agent', 'entry_point': 'main'}, 'PfeifferAgent': {'module': 'socs.agents.pfeiffer_tpg366.agent', 'entry_point': 'main'}, 'PfeifferTC400Agent': {'module': 'socs.agents.pfeiffer_tc400.agent', 'entry_point': 'main'}, 'PysmurfController': {'module': 'socs.agents.pysmurf_controller.agent', 'entry_point': 'main'}, From 9b225d99ff9f014240000ea9fb7ccbc2aed34650 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Sat, 28 Oct 2023 15:10:58 +0000 Subject: [PATCH 03/13] added docs and updated docstrings --- docs/agents/meinberg_syncbox_agent.rst | 99 +++++++++++++++++ socs/agents/meinberg_syncbox/agent.py | 148 ++++++++++++++++++++++--- 2 files changed, 234 insertions(+), 13 deletions(-) create mode 100644 docs/agents/meinberg_syncbox_agent.rst diff --git a/docs/agents/meinberg_syncbox_agent.rst b/docs/agents/meinberg_syncbox_agent.rst new file mode 100644 index 000000000..5bc84ce3c --- /dev/null +++ b/docs/agents/meinberg_syncbox_agent.rst @@ -0,0 +1,99 @@ +.. highlight:: rst + +.. _meinberg_syncbox: + +==================== +Meinberg Syncbox Agent +==================== + +The Meinberg Syncbox Agent is an OCS Agent which monitors the Meinberg syncbox, the +Monitoring is performed via SNMP. + +.. argparse:: + :filename: ../socs/agents/meinberg_syncbox/agent.py + :func: add_agent_args + :prog: python3 agent.py + +Configuration File Examples +--------------------------- +Below are configuration examples for the ocs config file and for running the +Agent in a docker container. + +OCS Site Config +``````````````` + +To configure the Meinberg Syncbox Agent we need to add a MeinbergSyncboxAgent +block to our ocs configuration file. Here is an example configuration block +using all of the available arguments:: + + {'agent-class': 'MeinbergSyncboxAgent', + 'instance-id': 'timing-syncbox', + 'arguments': [['--address', '192.168.2.166'], + ['--port', 161], + ['--mode', 'acq'], + ['--snmp-version', 1]]}, + +.. note:: + The ``--address`` argument should be the address of the syncbox on the network. + This is not the main Meinberg M1000 device. + +Docker Compose +`````````````` + +The Meinberg Syncbox Agent should be configured to run in a Docker container. An +example docker-compose service configuration is shown here:: + + ocs-timing-syncbox: + image: simonsobs/socs:latest + hostname: ocs-docker + network_mode: "host" + volumes: + - ${OCS_CONFIG_DIR}:/config:ro + environment: + - INSTANCE_ID=timing-syncbox + - SITE_HUB=ws://127.0.0.1:8001/ws + - SITE_HTTP=http://127.0.0.1:8001/call + - LOGLEVEL=info + + +The ``LOGLEVEL`` environment variable can be used to set the log level for +debugging. The default level is "info". + +Description +----------- +The Meinberg syncbox synchronizes to GPS and distributes timing mostly over +the network using PTP. + +The Meinberg Syncbox Agent actively issues SNMP GET commands to request the +status from several Object Identifiers (OIDs) specified by the syncbox +provided Management Information Base (MIB). +This MIB has been converted from the original .mib format to a .py format that +is consumable via pysnmp and is provided by socs. + +Agent Fields +```````````` +The fields returned by the Agent are built from the SNMP GET responses from the +syncbox. The field names consist of the OID name and the last value of the OID, +which often serves as an index for duplicate pieces of hardware that share a +OID string, i.e. redundant outputs on the OID "mbgSyncboxN2XOutputMode". This +results in field names such as "mbgSyncboxN2XOutputMode_1" and +"mbgSyncboxN2XOutputMode_2". + +These queries mostly return integers which map to some state. These integers +get decoded into their corresponding string representations and stored in the +OCS Agent Process' session.data object. For more details on this structure, see +the Agent API below. For information about the states corresponding to these +values, refer to the MIB file. + +Agent API +--------- + +.. autoclass:: socs.agents.meinberg_syncbox.agent.MeinbergSyncboxAgent + :members: + +Supporting APIs +---------------- + +.. autoclass:: socs.agents.meinberg_syncbox.agent.update_cache + :members: + :noindex: diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index 83e3e2572..499c64ecd 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -72,10 +72,10 @@ def _build_message(get_result, time, blockname): ---------- get_result : pysnmp.smi.rfc1902.ObjectType Result from a pysnmp GET command. - names : list - List of strings for outlet names time : float Timestamp for when the SNMP GET was issued. + blockname : string + Blockname for feed. Returns ------- @@ -111,11 +111,13 @@ def update_cache(get_result, timestamp): the OID values, name, and description (decoded string). An example for a single OID, with connection status and timestamp information:: - {"outletStatus_0": {"status": 1, - "name": Outlet-1, - "description": "on"}, + {"mbgSyncboxN2XCurrentRefSource_0": {"status": 'PTP', + "description": 'PTP'}} + + Additionally there is connection status and timestamp information under:: + "syncbox_connection": {"last_attempt": 1598543359.6326838, - "connected": True}, + "connected": True}, "timestamp": 1656085022.680916} Parameters @@ -202,26 +204,146 @@ def acq(self, session, params=None): **Process** - Fetch values from the syncbox via SNMP. + Parameters + ---------- + test_mode : bool, optional + Run the Process loop only once. Meant only for testing. + Default is False. + Notes ----- The most recent data collected is stored in session.data in the structure:: >>> response.session['data'] - {'outletStatus_0': + {'mbgSyncboxN2XSerialNumber_0': + {'status': '009811006890', + 'description': '009811006890'}, + 'mbgSyncboxN2XFirmwareRevision_0': + {'status': '1.20 ', + 'description': '1.20 '}, + 'mbgSyncboxN2XSystemTime_0': + {'status': '2023-10-28 14:25:54 UTC', + 'description': '2023-10-28 14:25:54 UTC'}, + 'mbgSyncboxN2XCurrentRefSource_0': + {'status': 'PTP', + 'description': 'PTP'}, + 'mbgSyncboxN2XPtpProfile_0': + {'status': 0, + 'description': 'none'}, + 'mbgSyncboxN2XPtpNwProt_0': {'status': 1, - 'name': 'Outlet-1', - 'description': 'on'}, - 'outletStatus_1': + 'description': 'ipv4'}, + 'mbgSyncboxN2XPtpPortState_0': + {'status': 9, + 'description': 'slave'}, + 'mbgSyncboxN2XPtpDelayMechanism_0': {'status': 0, - 'name': 'Outlet-2', - 'description': 'off'}, - ... + 'description': 'e2e'}, + 'mbgSyncboxN2XPtpDelayRequestInterval_0': + {'status': 1, + 'description': '1'}, + 'mbgSyncboxN2XPtpTimescale_0': + {'status': 0, + 'description': 'tai'}, + 'mbgSyncboxN2XPtpUTCOffset_0': + {'status': '37 sec', + 'description': '37 sec'}, + 'mbgSyncboxN2XPtpLeapSecondAnnounced_0': + {'status': 'no', + 'description': 'no'}, + 'mbgSyncboxN2XPtpGrandmasterClockID_0': + {'status': 'EC:46:70:FF:FE:0A:AB:FE', + 'description': 'EC:46:70:FF:FE:0A:AB:FE'}, + 'mbgSyncboxN2XPtpGrandmasterTimesource_0': + {'status': 32, + 'description': 'gps'}, + 'mbgSyncboxN2XPtpGrandmasterPriority1_0': + {'status': 64, + 'description': '64'}, + 'mbgSyncboxN2XPtpGrandmasterClockClass_0': + {'status': 6, + 'description': '6'}, + 'mbgSyncboxN2XPtpGrandmasterClockAccuracy_0': + {'status': 33, 'description': + 'accurateToWithin100ns'}, + 'mbgSyncboxN2XPtpGrandmasterClockVariance_0': + {'status': 13563, + 'description': '13563'}, + 'mbgSyncboxN2XPtpOffsetToGrandmaster_0': + {'status': '10 ns', + 'description': '10 ns'}, + 'mbgSyncboxN2XPtpMeanPathDelay_0': + {'status': '875 ns', + 'description': '875 ns'}, + 'mbgSyncboxN2XOutputMode_1': + {'status': 4, + 'description': 'pulsePerSecond'}, 'syncbox_connection': {'last_attempt': 1656085022.680916, 'connected': True}, 'timestamp': 1656085022.680916, 'address': '10.10.10.50'} + + Some relevant options and units for the above OIDs:: + + mbgSyncboxN2XPtpProfile:: + Options:: none(0), + power(1), + telecom(2) + mbgSyncboxN2XPtpNwProt:: + Options:: unknown(0), + ipv4(1), + ipv6(2), + ieee802-3(3), + deviceNet(4), + controlNet(5), + profiNet(6) + mbgSyncboxN2XPtpPortState:: + Options:: uninitialized(0), + initializing(1), + faulty(2), + disabled(3), + listening(4), + preMaster(5), + master(6), + passive(7), + uncalibrated(8), + slave(9) + mbgSyncboxN2XPtpDelayMechanism:: + Options:: e2e(0), + p2p(1) + mbgSyncboxN2XPtpTimescale:: + Options:: tai(0), + arb(1) + mbgSyncboxN2XPtpGrandmasterTimesource:: + Options:: atomicClock(16), + gps(32), + terrestrialRadio(48), + ptp(64), + ntp(80), + handSet(96), + other(144), + internalOscillator(160) + mbgSyncboxN2XPtpGrandmasterClockAccuracy:: + Options:: accurateToWithin25ns(32), + accurateToWithin100ns(33), + accurateToWithin250ns(34), + accurateToWithin1us(35), + accurateToWithin2Point5us(36), + accurateToWithin10us(37), + accurateToWithin25us(38), + accurateToWithin100us(39), + accurateToWithin250us(40), + accurateToWithin1ms(41), + accurateToWithin2Point5ms(42), + accurateToWithin10ms(43), + accurateToWithin25ms(44), + accurateToWithin100ms(45), + accurateToWithin250ms(46), + accurateToWithin1s(47), + accurateToWithin10s(48), + accurateToGreaterThan10s(49) """ session.set_status('running') From 61b1792b35f26359ec09a6f61d1d2dba73a58702 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Oct 2023 15:15:15 +0000 Subject: [PATCH 04/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- socs/agents/meinberg_syncbox/agent.py | 12 +-- socs/mibs/MBG-SYNCBOX-N2X-MIB.py | 137 +++++++++++++++++--------- 2 files changed, 97 insertions(+), 52 deletions(-) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index 499c64ecd..ffee86d2e 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -131,7 +131,7 @@ def update_cache(get_result, timestamp): # Return disconnected if SNMP response is empty if get_result is None: oid_cache['syncbox_connection'] = {'last_attempt': time.time(), - 'connected': False} + 'connected': False} return oid_cache for item in get_result: @@ -143,7 +143,7 @@ def update_cache(get_result, timestamp): oid_cache[field_name] = {"status": oid_value} oid_cache[field_name]["description"] = oid_description oid_cache['syncbox_connection'] = {'last_attempt': time.time(), - 'connected': True} + 'connected': True} oid_cache['timestamp'] = timestamp return oid_cache @@ -229,7 +229,7 @@ def acq(self, session, params=None): {'status': 'PTP', 'description': 'PTP'}, 'mbgSyncboxN2XPtpProfile_0': - {'status': 0, + {'status': 0, 'description': 'none'}, 'mbgSyncboxN2XPtpNwProt_0': {'status': 1, @@ -498,9 +498,9 @@ def main(args=None): agent, runner = ocs_agent.init_site_agent(args) p = MeinbergSyncboxAgent(agent, - address=args.address, - port=int(args.port), - version=int(args.snmp_version)) + address=args.address, + port=int(args.port), + version=int(args.snmp_version)) agent.register_process("acq", p.acq, diff --git a/socs/mibs/MBG-SYNCBOX-N2X-MIB.py b/socs/mibs/MBG-SYNCBOX-N2X-MIB.py index d10dcefe9..e633abb34 100644 --- a/socs/mibs/MBG-SYNCBOX-N2X-MIB.py +++ b/socs/mibs/MBG-SYNCBOX-N2X-MIB.py @@ -3,7 +3,7 @@ # ASN.1 source file://./MBG-SYNCBOX-N2X-MIB.mib # Produced by pysmi-1.1.13 at Fri Oct 27 11:50:07 2023 # On host HAWKING platform Linux version 5.15.90.1-microsoft-standard-WSL2 by user davidvng -# Using Python version 3.8.8 (default, Apr 13 2021, 19:58:26) +# Using Python version 3.8.8 (default, Apr 13 2021, 19:58:26) # Integer, ObjectIdentifier, OctetString = mibBuilder.importSymbols("ASN1", "Integer", "ObjectIdentifier", "OctetString") NamedValues, = mibBuilder.importSymbols("ASN1-ENUMERATION", "NamedValues") @@ -14,98 +14,143 @@ TextualConvention, DisplayString = mibBuilder.importSymbols("SNMPv2-TC", "TextualConvention", "DisplayString") mbgSyncboxN2X = ModuleIdentity((1, 3, 6, 1, 4, 1, 5597, 40)) mbgSyncboxN2X.setRevisions(('2013-09-03 00:00',)) -if mibBuilder.loadTexts: mbgSyncboxN2X.setLastUpdated('201309030000Z') -if mibBuilder.loadTexts: mbgSyncboxN2X.setOrganization('www.meinberg.de') +if mibBuilder.loadTexts: + mbgSyncboxN2X.setLastUpdated('201309030000Z') +if mibBuilder.loadTexts: + mbgSyncboxN2X.setOrganization('www.meinberg.de') mbgSyncboxN2XGeneral = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 0)) mbgSyncboxN2XSerialNumber = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 1), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XSerialNumber.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XSerialNumber.setStatus('current') mbgSyncboxN2XFirmwareRevision = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 2), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XFirmwareRevision.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XFirmwareRevision.setStatus('current') mbgSyncboxN2XSystemTime = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 3), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XSystemTime.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XSystemTime.setStatus('current') mbgSyncboxN2XCurrentRefSource = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 0, 4), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XCurrentRefSource.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XCurrentRefSource.setStatus('current') mbgSyncboxN2XNetworkTimeProtocol = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 1)) mbgSyncboxN2XNtpSyncStatus = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 1, 1), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpSyncStatus.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpSyncStatus.setStatus('current') mbgSyncboxN2XNtpSystemPeer = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 1, 2), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpSystemPeer.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpSystemPeer.setStatus('current') mbgSyncboxN2XNtpStratum = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 1, 3), Unsigned32()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpStratum.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpStratum.setStatus('current') mbgSyncboxN2XNtpRefSourceTable = MibTable((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4), ) -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceTable.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceTable.setStatus('current') mbgSyncboxN2XNtpRefSourceTableEntry = MibTableRow((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1), ).setIndexNames((0, "MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XNtpRefSourceIndex")) -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceTableEntry.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceTableEntry.setStatus('current') mbgSyncboxN2XNtpRefSourceIndex = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 1), Unsigned32().subtype(subtypeSpec=ValueRangeConstraint(0, 6))) -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceIndex.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceIndex.setStatus('current') mbgSyncboxN2XNtpRefSourceHostname = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 2), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceHostname.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceHostname.setStatus('current') mbgSyncboxN2XNtpRefSourceStratum = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 3), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceStratum.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceStratum.setStatus('current') mbgSyncboxN2XNtpRefSourceReferenceID = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 4), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceReferenceID.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceReferenceID.setStatus('current') mbgSyncboxN2XNtpRefSourceReach = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 5), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceReach.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceReach.setStatus('current') mbgSyncboxN2XNtpRefSourceCurrPoll = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 6), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceCurrPoll.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceCurrPoll.setStatus('current') mbgSyncboxN2XNtpRefSourceMinPoll = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 7), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceMinPoll.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceMinPoll.setStatus('current') mbgSyncboxN2XNtpRefSourceMaxPoll = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 8), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceMaxPoll.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceMaxPoll.setStatus('current') mbgSyncboxN2XNtpRefSourceConfigOptions = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 9), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceConfigOptions.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceConfigOptions.setStatus('current') mbgSyncboxN2XNtpRefSourcePathDelay = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 10), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourcePathDelay.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourcePathDelay.setStatus('current') mbgSyncboxN2XNtpRefSourceOffset = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 11), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceOffset.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceOffset.setStatus('current') mbgSyncboxN2XNtpRefSourceJitter = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 1, 4, 1, 12), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XNtpRefSourceJitter.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XNtpRefSourceJitter.setStatus('current') mbgSyncboxN2XPrecisionTimeProtocol = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 2)) mbgSyncboxN2XPtpProfile = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 1), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2))).clone(namedValues=NamedValues(("none", 0), ("power", 1), ("telecom", 2)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpProfile.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpProfile.setStatus('current') mbgSyncboxN2XPtpNwProt = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 2), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2, 3, 4, 5, 6))).clone(namedValues=NamedValues(("unknown", 0), ("ipv4", 1), ("ipv6", 2), ("ieee802-3", 3), ("deviceNet", 4), ("controlNet", 5), ("profiNet", 6)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpNwProt.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpNwProt.setStatus('current') mbgSyncboxN2XPtpPortState = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 3), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))).clone(namedValues=NamedValues(("uninitialized", 0), ("initializing", 1), ("faulty", 2), ("disabled", 3), ("listening", 4), ("preMaster", 5), ("master", 6), ("passive", 7), ("uncalibrated", 8), ("slave", 9)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpPortState.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpPortState.setStatus('current') mbgSyncboxN2XPtpDelayMechanism = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 4), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1))).clone(namedValues=NamedValues(("e2e", 0), ("p2p", 1)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpDelayMechanism.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpDelayMechanism.setStatus('current') mbgSyncboxN2XPtpDelayRequestInterval = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 5), Integer32()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpDelayRequestInterval.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpDelayRequestInterval.setStatus('current') mbgSyncboxN2XPtpTimescale = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 6), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1))).clone(namedValues=NamedValues(("tai", 0), ("arb", 1)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpTimescale.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpTimescale.setStatus('current') mbgSyncboxN2XPtpUTCOffset = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 7), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpUTCOffset.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpUTCOffset.setStatus('current') mbgSyncboxN2XPtpLeapSecondAnnounced = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 8), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpLeapSecondAnnounced.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpLeapSecondAnnounced.setStatus('current') mbgSyncboxN2XPtpGrandmasterClockID = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 9), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockID.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpGrandmasterClockID.setStatus('current') mbgSyncboxN2XPtpGrandmasterTimesource = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 10), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(16, 32, 48, 64, 80, 96, 144, 160))).clone(namedValues=NamedValues(("atomicClock", 16), ("gps", 32), ("terrestrialRadio", 48), ("ptp", 64), ("ntp", 80), ("handSet", 96), ("other", 144), ("internalOscillator", 160)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterTimesource.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpGrandmasterTimesource.setStatus('current') mbgSyncboxN2XPtpGrandmasterPriority1 = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 11), Unsigned32()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterPriority1.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpGrandmasterPriority1.setStatus('current') mbgSyncboxN2XPtpGrandmasterClockClass = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 12), Unsigned32()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockClass.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpGrandmasterClockClass.setStatus('current') mbgSyncboxN2XPtpGrandmasterClockAccuracy = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 13), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49))).clone(namedValues=NamedValues(("accurateToWithin25ns", 32), ("accurateToWithin100ns", 33), ("accurateToWithin250ns", 34), ("accurateToWithin1us", 35), ("accurateToWithin2Point5us", 36), ("accurateToWithin10us", 37), ("accurateToWithin25us", 38), ("accurateToWithin100us", 39), ("accurateToWithin250us", 40), ("accurateToWithin1ms", 41), ("accurateToWithin2Point5ms", 42), ("accurateToWithin10ms", 43), ("accurateToWithin25ms", 44), ("accurateToWithin100ms", 45), ("accurateToWithin250ms", 46), ("accurateToWithin1s", 47), ("accurateToWithin10s", 48), ("accurateToGreaterThan10s", 49)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockAccuracy.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpGrandmasterClockAccuracy.setStatus('current') mbgSyncboxN2XPtpGrandmasterClockVariance = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 14), Unsigned32()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterClockVariance.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpGrandmasterClockVariance.setStatus('current') mbgSyncboxN2XPtpGrandmasterPriority2 = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 15), Unsigned32()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpGrandmasterPriority2.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpGrandmasterPriority2.setStatus('current') mbgSyncboxN2XPtpOffsetToGrandmaster = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 16), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpOffsetToGrandmaster.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpOffsetToGrandmaster.setStatus('current') mbgSyncboxN2XPtpMeanPathDelay = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 2, 17), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XPtpMeanPathDelay.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XPtpMeanPathDelay.setStatus('current') mbgSyncboxN2XOutputs = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 3)) mbgSyncboxN2XOutputsTable = MibTable((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1), ) -if mibBuilder.loadTexts: mbgSyncboxN2XOutputsTable.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XOutputsTable.setStatus('current') mbgSyncboxN2XOutputsTableEntry = MibTableRow((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1, 1), ).setIndexNames((0, "MBG-SYNCBOX-N2X-MIB", "mbgSyncboxN2XOutputIndex")) -if mibBuilder.loadTexts: mbgSyncboxN2XOutputsTableEntry.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XOutputsTableEntry.setStatus('current') mbgSyncboxN2XOutputIndex = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1, 1, 1), Unsigned32().subtype(subtypeSpec=ValueRangeConstraint(0, 2))) -if mibBuilder.loadTexts: mbgSyncboxN2XOutputIndex.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XOutputIndex.setStatus('current') mbgSyncboxN2XOutputMode = MibTableColumn((1, 3, 6, 1, 4, 1, 5597, 40, 3, 1, 1, 2), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))).clone(namedValues=NamedValues(("idle", 0), ("timer", 1), ("singleShot", 2), ("cyclicPulse", 3), ("pulsePerSecond", 4), ("pulsePerMinute", 5), ("pulsePerHour", 6), ("emulatedDCF77", 7), ("positionOK", 8), ("timeSync", 9), ("allSync", 10), ("timecode", 11), ("timestring", 12), ("tenMHz", 13), ("emulatedDCF77M59", 14), ("synthesizer", 15), ("timeSlots", 16)))).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XOutputMode.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XOutputMode.setStatus('current') mbgSyncboxN2XSerialString = MibScalar((1, 3, 6, 1, 4, 1, 5597, 40, 3, 2), DisplayString()).setMaxAccess("readonly") -if mibBuilder.loadTexts: mbgSyncboxN2XSerialString.setStatus('current') +if mibBuilder.loadTexts: + mbgSyncboxN2XSerialString.setStatus('current') mbgSyncboxN2XConformance = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 10)) mbgSyncboxN2XCompliances = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 10, 0)) mbgSyncboxN2XGroups = MibIdentifier((1, 3, 6, 1, 4, 1, 5597, 40, 10, 1)) From bf8f19b3a3af5c7679b403e90bdbbcc048d94d42 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Sat, 28 Oct 2023 15:22:16 +0000 Subject: [PATCH 05/13] fix precommit --- socs/agents/meinberg_syncbox/agent.py | 1 - socs/mibs/MBG-SYNCBOX-N2X-MIB.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index ffee86d2e..1d27ea072 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -8,7 +8,6 @@ from ocs.ocs_twisted import TimeoutLock from twisted.internet.defer import inlineCallbacks -import socs from socs.snmp import SNMPTwister # For logging diff --git a/socs/mibs/MBG-SYNCBOX-N2X-MIB.py b/socs/mibs/MBG-SYNCBOX-N2X-MIB.py index e633abb34..d062b776f 100644 --- a/socs/mibs/MBG-SYNCBOX-N2X-MIB.py +++ b/socs/mibs/MBG-SYNCBOX-N2X-MIB.py @@ -5,6 +5,10 @@ # On host HAWKING platform Linux version 5.15.90.1-microsoft-standard-WSL2 by user davidvng # Using Python version 3.8.8 (default, Apr 13 2021, 19:58:26) # +from pysnmp.smi import builder + +mibBuilder = builder.MibBuilder() + Integer, ObjectIdentifier, OctetString = mibBuilder.importSymbols("ASN1", "Integer", "ObjectIdentifier", "OctetString") NamedValues, = mibBuilder.importSymbols("ASN1-ENUMERATION", "NamedValues") ValueSizeConstraint, ConstraintsIntersection, ValueRangeConstraint, SingleValueConstraint, ConstraintsUnion = mibBuilder.importSymbols("ASN1-REFINEMENT", "ValueSizeConstraint", "ConstraintsIntersection", "ValueRangeConstraint", "SingleValueConstraint", "ConstraintsUnion") From 28b41140ca2674cd0a2a69927f0024cca3dac993 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Mon, 13 Nov 2023 18:15:37 +0000 Subject: [PATCH 06/13] snmp asyncio attempt --- socs/agents/meinberg_syncbox/agent.py | 78 ++++++++++++++++----------- socs/snmp.py | 49 ++++++++++++++++- 2 files changed, 94 insertions(+), 33 deletions(-) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index 1d27ea072..6622a604a 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -10,8 +10,11 @@ from socs.snmp import SNMPTwister +from guppy import hpy +import asyncio + # For logging -txaio.use_twisted() +# txaio.use_asyncio() def _extract_oid_field_and_value(get_result): @@ -197,7 +200,8 @@ def __init__(self, agent, address, port=161, version=1): buffer_time=0) @ocs_agent.param('test_mode', default=False, type=bool) - @inlineCallbacks + # @asyncio.coroutine + # @inlineCallbacks def acq(self, session, params=None): """acq() @@ -387,12 +391,13 @@ def acq(self, session, params=None): for oid in oids: main_get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, 0)) get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, 0)) - - general_get_result = yield self.snmp.get(get_list, self.version) - if general_get_result is None: - self.connected = False - continue - self.connected = True + loop = asyncio.get_event_loop() + # general_get_result = loop.run_until_complete(self.snmp.run(get_list, self.version)) + # if general_get_result is None: + # self.connected = False + # print("failed general") + # continue + # self.connected = True output_oids = ['mbgSyncboxN2XOutputMode'] @@ -403,47 +408,58 @@ def acq(self, session, params=None): for oid in output_oids: main_get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, i + 1)) get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, i + 1)) - output_get_result = yield self.snmp.get(get_list, self.version) - output_get_results.append(output_get_result) - - get_results = [] - # Issue SNMP GET command - for get in main_get_list: - get_result = yield self.snmp.get([get], self.version) - if get_result is None: - self.connected = False - continue - self.connected = True - get_results.append(get_result[0]) + # output_get_result = loop.run_until_complete(self.snmp.run(get_list, self.version)) + # output_get_results.append(output_get_result) + + # get_results = [] + # # Issue SNMP GET command + # for get in main_get_list: + # get_result = loop.run_until_complete(self.snmp.run([get], self.version)) + # if get_result is None: + # self.connected = False + # print("failed main") + # continue + # self.connected = True + # get_results.append(get_result[0]) + + get_result = loop.run_until_complete(self.snmp.run(main_get_list, self.version)) + if get_result is None: + self.connected = False + print("failed main") + continue + self.connected = True # Do not publish if syncbox connection has dropped try: # Update session.data - session.data = update_cache(get_results, read_time) + session.data = update_cache(get_result, read_time) # oid_cache['address'] = self.address # session.data = oid_cache - self.log.debug("{data}", data=session.data) + self.log.info("{data}", data=session.data) if not self.connected: raise ConnectionError('No SNMP response. Check your connection.') self.lastGet = time.time() # Publish to feed - if general_get_result is not None: - message = _build_message(general_get_result, read_time, 'syncbox') - self.log.debug("{msg}", msg=message) + if get_result is not None: + message = _build_message(get_result, read_time, 'syncbox') + self.log.info("{msg}", msg=message) session.app.publish_to_feed('syncbox', message) - for i, result in enumerate(output_get_results): - if result is not None: - blockname = f'output_{i}' - message = _build_message(result, read_time, blockname) - self.log.debug("{msg}", msg=message) - session.app.publish_to_feed('syncbox', message) + # for i, result in enumerate(output_get_results): + # if result is not None: + # blockname = f'output_{i}' + # message = _build_message(result, read_time, blockname) + # self.log.debug("{msg}", msg=message) + # session.app.publish_to_feed('syncbox', message) except ConnectionError as e: self.log.error(f'{e}') yield dsleep(1) self.log.info('Trying to reconnect.') + h = hpy() + print(h.heap()) + if params['test_mode']: break diff --git a/socs/snmp.py b/socs/snmp.py index 3f64f882c..6109a35c1 100644 --- a/socs/snmp.py +++ b/socs/snmp.py @@ -1,14 +1,15 @@ import os import txaio -from pysnmp.hlapi.twisted import (CommunityData, ContextData, ObjectIdentity, +import asyncio +from pysnmp.hlapi.asyncio import (CommunityData, ContextData, ObjectIdentity, ObjectType, SnmpEngine, UdpTransportTarget, UsmUserData, getCmd, setCmd) from socs import mibs # For logging -txaio.use_twisted() +# txaio.use_asyncio() # https://pysnmp.readthedocs.io/en/latest/faq/pass-custom-mib-to-manager.html @@ -157,6 +158,50 @@ def get(self, oid_list, version): return datagram + @asyncio.coroutine + def run(self, oid_list, version): + oid_list = [ObjectType(ObjectIdentity(*x).addMibSource(MIB_SOURCE)) + if isinstance(x, tuple) + else x + for x + in oid_list] + + if version == 1: + version_object = CommunityData('public', mpModel=0) # SNMPv1 + elif version == 2: + version_object = CommunityData('public') # SNMPv2c + elif version == 3: + version_object = UsmUserData('ocs') # SNMPv3 (no auth, no privacy) + else: + raise ValueError(f'SNMP version {version} not supported.') + + iterator = getCmd( + self.snmp_engine, + version_object, + self.udp_transport, + ContextData(), + *oid_list + ) + + errorIndication, errorStatus, errorIndex, varBinds = yield from iterator + + if errorIndication: + print(errorIndication) + + elif errorStatus: + print('%s at %s' % ( + errorStatus.prettyPrint(), + errorIndex and varBinds[int(errorIndex) - 1][0] or '?' + ) + ) + else: + for varBind in varBinds: + print(' = '.join([x.prettyPrint() for x in varBind])) + + self.snmp_engine.transportDispatcher.closeDispatcher() + + return varBinds + def set(self, oid_list, version, setvalue, community_name='private'): """Issue a setCmd to set SNMP OID states. See `Modifying MIB variables`_ for more info on setting OID states. From 4048c9febbe5600f8beb640220b609c86de337e4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:09:42 +0000 Subject: [PATCH 07/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- socs/agents/meinberg_syncbox/agent.py | 5 ++--- socs/snmp.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index 6622a604a..d4604f6f2 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -1,18 +1,17 @@ import argparse +import asyncio import os import time import txaio from autobahn.twisted.util import sleep as dsleep +from guppy import hpy from ocs import ocs_agent, site_config from ocs.ocs_twisted import TimeoutLock from twisted.internet.defer import inlineCallbacks from socs.snmp import SNMPTwister -from guppy import hpy -import asyncio - # For logging # txaio.use_asyncio() diff --git a/socs/snmp.py b/socs/snmp.py index 6109a35c1..cc878a807 100644 --- a/socs/snmp.py +++ b/socs/snmp.py @@ -1,7 +1,7 @@ +import asyncio import os import txaio -import asyncio from pysnmp.hlapi.asyncio import (CommunityData, ContextData, ObjectIdentity, ObjectType, SnmpEngine, UdpTransportTarget, UsmUserData, getCmd, setCmd) @@ -193,7 +193,7 @@ def run(self, oid_list, version): errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?' ) - ) + ) else: for varBind in varBinds: print(' = '.join([x.prettyPrint() for x in varBind])) From 3564a1bbfa0bf6b2beb216c383e25833d5ce0c8e Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Mon, 22 Apr 2024 21:27:21 +0000 Subject: [PATCH 08/13] agent updates and address comments --- docs/agents/meinberg_syncbox_agent.rst | 4 +- socs/agents/meinberg_syncbox/agent.py | 152 ++++++++++--------------- socs/snmp.py | 51 +-------- 3 files changed, 63 insertions(+), 144 deletions(-) diff --git a/docs/agents/meinberg_syncbox_agent.rst b/docs/agents/meinberg_syncbox_agent.rst index 5bc84ce3c..b4c7e4b43 100644 --- a/docs/agents/meinberg_syncbox_agent.rst +++ b/docs/agents/meinberg_syncbox_agent.rst @@ -61,8 +61,8 @@ debugging. The default level is "info". Description ----------- -The Meinberg syncbox synchronizes to GPS and distributes timing mostly over -the network using PTP. +The Meinberg syncbox synchronizes to the M1000 from PTP and distributes signal +to attached devices in various formats (IRIG, PPS, etc). The Meinberg Syncbox Agent actively issues SNMP GET commands to request the status from several Object Identifiers (OIDs) specified by the syncbox diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index d4604f6f2..667fa8365 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -1,11 +1,9 @@ import argparse -import asyncio import os import time import txaio from autobahn.twisted.util import sleep as dsleep -from guppy import hpy from ocs import ocs_agent, site_config from ocs.ocs_twisted import TimeoutLock from twisted.internet.defer import inlineCallbacks @@ -13,7 +11,7 @@ from socs.snmp import SNMPTwister # For logging -# txaio.use_asyncio() +txaio.use_twisted() def _extract_oid_field_and_value(get_result): @@ -66,7 +64,7 @@ def _extract_oid_field_and_value(get_result): return field_name, oid_value, oid_description -def _build_message(get_result, time, blockname): +def _build_message(get_result, time): """Build the message for publication on an OCS Feed. Parameters @@ -75,8 +73,6 @@ def _build_message(get_result, time, blockname): Result from a pysnmp GET command. time : float Timestamp for when the SNMP GET was issued. - blockname : string - Blockname for feed. Returns ------- @@ -84,7 +80,7 @@ def _build_message(get_result, time, blockname): OCS Feed formatted message for publishing """ message = { - 'block_name': blockname, + 'block_name': 'syncbox', 'timestamp': time, 'data': {} } @@ -93,7 +89,8 @@ def _build_message(get_result, time, blockname): field_name, oid_value, oid_description = _extract_oid_field_and_value(item) if oid_value is None: - continue + oid_value = 'No SNMP response' + oid_description = 'No SNMP response' message['data'][field_name] = oid_value message['data'][field_name + "_description"] = oid_description @@ -190,6 +187,30 @@ def __init__(self, agent, address, port=161, version=1): self.lastGet = 0 self.sample_period = 60 + # Create the list of OIDs to send get commands + self.oids = ['mbgSyncboxN2XSerialNumber', + 'mbgSyncboxN2XFirmwareRevision', + 'mbgSyncboxN2XSystemTime', + 'mbgSyncboxN2XCurrentRefSource', + 'mbgSyncboxN2XPtpProfile', + 'mbgSyncboxN2XPtpNwProt', + 'mbgSyncboxN2XPtpPortState', + 'mbgSyncboxN2XPtpDelayMechanism', + 'mbgSyncboxN2XPtpDelayRequestInterval', + 'mbgSyncboxN2XPtpTimescale', + 'mbgSyncboxN2XPtpUTCOffset', + 'mbgSyncboxN2XPtpLeapSecondAnnounced', + 'mbgSyncboxN2XPtpGrandmasterClockID', + 'mbgSyncboxN2XPtpGrandmasterTimesource', + 'mbgSyncboxN2XPtpGrandmasterPriority1', + 'mbgSyncboxN2XPtpGrandmasterClockClass', + 'mbgSyncboxN2XPtpGrandmasterClockAccuracy', + 'mbgSyncboxN2XPtpGrandmasterClockVariance', + 'mbgSyncboxN2XPtpOffsetToGrandmaster', + 'mbgSyncboxN2XPtpMeanPathDelay'] + self.output_oids = ['mbgSyncboxN2XOutputMode'] + self.mib = 'MBG-SYNCBOX-N2X-MIB' + agg_params = { 'frame_length': 10 * 60 # [sec] } @@ -199,8 +220,7 @@ def __init__(self, agent, address, port=161, version=1): buffer_time=0) @ocs_agent.param('test_mode', default=False, type=bool) - # @asyncio.coroutine - # @inlineCallbacks + @inlineCallbacks def acq(self, session, params=None): """acq() @@ -348,7 +368,6 @@ def acq(self, session, params=None): accurateToGreaterThan10s(49) """ - session.set_status('running') self.is_streaming = True while self.is_streaming: yield dsleep(1) @@ -362,102 +381,47 @@ def acq(self, session, params=None): if (read_time - self.lastGet) < self.sample_period: continue - main_get_list = [] get_list = [] - # Create the list of OIDs to send get commands - oids = ['mbgSyncboxN2XSerialNumber', - 'mbgSyncboxN2XFirmwareRevision', - 'mbgSyncboxN2XSystemTime', - 'mbgSyncboxN2XCurrentRefSource', - 'mbgSyncboxN2XPtpProfile', - 'mbgSyncboxN2XPtpNwProt', - 'mbgSyncboxN2XPtpPortState', - 'mbgSyncboxN2XPtpDelayMechanism', - 'mbgSyncboxN2XPtpDelayRequestInterval', - 'mbgSyncboxN2XPtpTimescale', - 'mbgSyncboxN2XPtpUTCOffset', - 'mbgSyncboxN2XPtpLeapSecondAnnounced', - 'mbgSyncboxN2XPtpGrandmasterClockID', - 'mbgSyncboxN2XPtpGrandmasterTimesource', - 'mbgSyncboxN2XPtpGrandmasterPriority1', - 'mbgSyncboxN2XPtpGrandmasterClockClass', - 'mbgSyncboxN2XPtpGrandmasterClockAccuracy', - 'mbgSyncboxN2XPtpGrandmasterClockVariance', - 'mbgSyncboxN2XPtpOffsetToGrandmaster', - 'mbgSyncboxN2XPtpMeanPathDelay'] - - for oid in oids: - main_get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, 0)) - get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, 0)) - loop = asyncio.get_event_loop() - # general_get_result = loop.run_until_complete(self.snmp.run(get_list, self.version)) - # if general_get_result is None: - # self.connected = False - # print("failed general") - # continue - # self.connected = True - - output_oids = ['mbgSyncboxN2XOutputMode'] - - output_get_results = [] - outputs = 2 + # Create the lists of OIDs to send get commands + for oid in self.oids: + get_list.append([(self.mib, oid, 0)]) + + outputs = 3 # number of outputs on the syncbox for i in range(outputs): - get_list = [] - for oid in output_oids: - main_get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, i + 1)) - get_list.append(('MBG-SYNCBOX-N2X-MIB', oid, i + 1)) - # output_get_result = loop.run_until_complete(self.snmp.run(get_list, self.version)) - # output_get_results.append(output_get_result) - - # get_results = [] - # # Issue SNMP GET command - # for get in main_get_list: - # get_result = loop.run_until_complete(self.snmp.run([get], self.version)) - # if get_result is None: - # self.connected = False - # print("failed main") - # continue - # self.connected = True - # get_results.append(get_result[0]) - - get_result = loop.run_until_complete(self.snmp.run(main_get_list, self.version)) - if get_result is None: - self.connected = False - print("failed main") - continue - self.connected = True + for oid in self.output_oids: + get_list.append([(self.mib, oid, i)]) + + # Issue SNMP GET command + # The syncbox has a unique case this requires issuing GET commands + # one by one or else it will return the same data for each OID + get_result = yield self.snmp.get(get_list[0], self.version) + for get in get_list[1:]: + result = yield self.snmp.get(get, self.version) + if result is None: + self.connected = False + session.data['syncbox_connection'] = {'last_attempt': time.time(), + 'connected': False} + continue + get_result.extend(result) + self.connected = True # Do not publish if syncbox connection has dropped try: # Update session.data - session.data = update_cache(get_result, read_time) - # oid_cache['address'] = self.address - # session.data = oid_cache + oid_cache = update_cache(get_result, read_time) + oid_cache['address'] = self.address + session.data = oid_cache self.log.info("{data}", data=session.data) - if not self.connected: - raise ConnectionError('No SNMP response. Check your connection.') - self.lastGet = time.time() # Publish to feed - if get_result is not None: - message = _build_message(get_result, read_time, 'syncbox') - self.log.info("{msg}", msg=message) - session.app.publish_to_feed('syncbox', message) - # for i, result in enumerate(output_get_results): - # if result is not None: - # blockname = f'output_{i}' - # message = _build_message(result, read_time, blockname) - # self.log.debug("{msg}", msg=message) - # session.app.publish_to_feed('syncbox', message) - except ConnectionError as e: + message = _build_message(get_result, read_time) + self.log.info("{msg}", msg=message) + session.app.publish_to_feed('syncbox', message) + except Exception as e: self.log.error(f'{e}') yield dsleep(1) - self.log.info('Trying to reconnect.') - - h = hpy() - print(h.heap()) if params['test_mode']: break diff --git a/socs/snmp.py b/socs/snmp.py index cc878a807..816342efc 100644 --- a/socs/snmp.py +++ b/socs/snmp.py @@ -1,15 +1,14 @@ -import asyncio import os import txaio -from pysnmp.hlapi.asyncio import (CommunityData, ContextData, ObjectIdentity, +from pysnmp.hlapi.twisted import (CommunityData, ContextData, ObjectIdentity, ObjectType, SnmpEngine, UdpTransportTarget, UsmUserData, getCmd, setCmd) from socs import mibs # For logging -# txaio.use_asyncio() +txaio.use_twisted() # https://pysnmp.readthedocs.io/en/latest/faq/pass-custom-mib-to-manager.html @@ -158,50 +157,6 @@ def get(self, oid_list, version): return datagram - @asyncio.coroutine - def run(self, oid_list, version): - oid_list = [ObjectType(ObjectIdentity(*x).addMibSource(MIB_SOURCE)) - if isinstance(x, tuple) - else x - for x - in oid_list] - - if version == 1: - version_object = CommunityData('public', mpModel=0) # SNMPv1 - elif version == 2: - version_object = CommunityData('public') # SNMPv2c - elif version == 3: - version_object = UsmUserData('ocs') # SNMPv3 (no auth, no privacy) - else: - raise ValueError(f'SNMP version {version} not supported.') - - iterator = getCmd( - self.snmp_engine, - version_object, - self.udp_transport, - ContextData(), - *oid_list - ) - - errorIndication, errorStatus, errorIndex, varBinds = yield from iterator - - if errorIndication: - print(errorIndication) - - elif errorStatus: - print('%s at %s' % ( - errorStatus.prettyPrint(), - errorIndex and varBinds[int(errorIndex) - 1][0] or '?' - ) - ) - else: - for varBind in varBinds: - print(' = '.join([x.prettyPrint() for x in varBind])) - - self.snmp_engine.transportDispatcher.closeDispatcher() - - return varBinds - def set(self, oid_list, version, setvalue, community_name='private'): """Issue a setCmd to set SNMP OID states. See `Modifying MIB variables`_ for more info on setting OID states. @@ -252,4 +207,4 @@ def set(self, oid_list, version, setvalue, community_name='private'): datagram.addCallback(self._success).addErrback(self._failure) - return datagram + return datagram \ No newline at end of file From 848c6c854ee86c885803b9314c96344b18d599ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:27:43 +0000 Subject: [PATCH 09/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- socs/agents/meinberg_syncbox/agent.py | 4 ++-- socs/snmp.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index 667fa8365..7e3ff3122 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -387,7 +387,7 @@ def acq(self, session, params=None): for oid in self.oids: get_list.append([(self.mib, oid, 0)]) - outputs = 3 # number of outputs on the syncbox + outputs = 3 # number of outputs on the syncbox for i in range(outputs): for oid in self.output_oids: get_list.append([(self.mib, oid, i)]) @@ -401,7 +401,7 @@ def acq(self, session, params=None): if result is None: self.connected = False session.data['syncbox_connection'] = {'last_attempt': time.time(), - 'connected': False} + 'connected': False} continue get_result.extend(result) self.connected = True diff --git a/socs/snmp.py b/socs/snmp.py index 816342efc..3f64f882c 100644 --- a/socs/snmp.py +++ b/socs/snmp.py @@ -207,4 +207,4 @@ def set(self, oid_list, version, setvalue, community_name='private'): datagram.addCallback(self._success).addErrback(self._failure) - return datagram \ No newline at end of file + return datagram From d4be04cf97084333920a721537920fca5269dd02 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Wed, 24 Apr 2024 19:38:00 +0000 Subject: [PATCH 10/13] address more comments --- socs/agents/meinberg_syncbox/agent.py | 85 ++++++++++++++------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index 7e3ff3122..994a64d7c 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -89,8 +89,7 @@ def _build_message(get_result, time): field_name, oid_value, oid_description = _extract_oid_field_and_value(item) if oid_value is None: - oid_value = 'No SNMP response' - oid_description = 'No SNMP response' + continue message['data'][field_name] = oid_value message['data'][field_name + "_description"] = oid_description @@ -160,6 +159,8 @@ class MeinbergSyncboxAgent: SNMP port to issue GETs to, default to 161. version : int SNMP version for communication (1, 2, or 3), defaults to 1. + outputs : list of ints + List of outputs to monitor. Attributes ---------- @@ -172,7 +173,7 @@ class MeinbergSyncboxAgent: txaio logger object, created by the OCSAgent """ - def __init__(self, agent, address, port=161, version=1): + def __init__(self, agent, address, port=161, version=1, outputs=[1,2,3]): self.agent = agent self.is_streaming = False self.log = self.agent.log @@ -188,28 +189,38 @@ def __init__(self, agent, address, port=161, version=1): self.sample_period = 60 # Create the list of OIDs to send get commands - self.oids = ['mbgSyncboxN2XSerialNumber', - 'mbgSyncboxN2XFirmwareRevision', - 'mbgSyncboxN2XSystemTime', - 'mbgSyncboxN2XCurrentRefSource', - 'mbgSyncboxN2XPtpProfile', - 'mbgSyncboxN2XPtpNwProt', - 'mbgSyncboxN2XPtpPortState', - 'mbgSyncboxN2XPtpDelayMechanism', - 'mbgSyncboxN2XPtpDelayRequestInterval', - 'mbgSyncboxN2XPtpTimescale', - 'mbgSyncboxN2XPtpUTCOffset', - 'mbgSyncboxN2XPtpLeapSecondAnnounced', - 'mbgSyncboxN2XPtpGrandmasterClockID', - 'mbgSyncboxN2XPtpGrandmasterTimesource', - 'mbgSyncboxN2XPtpGrandmasterPriority1', - 'mbgSyncboxN2XPtpGrandmasterClockClass', - 'mbgSyncboxN2XPtpGrandmasterClockAccuracy', - 'mbgSyncboxN2XPtpGrandmasterClockVariance', - 'mbgSyncboxN2XPtpOffsetToGrandmaster', - 'mbgSyncboxN2XPtpMeanPathDelay'] - self.output_oids = ['mbgSyncboxN2XOutputMode'] - self.mib = 'MBG-SYNCBOX-N2X-MIB' + oids = ['mbgSyncboxN2XSerialNumber', + 'mbgSyncboxN2XFirmwareRevision', + 'mbgSyncboxN2XSystemTime', + 'mbgSyncboxN2XCurrentRefSource', + 'mbgSyncboxN2XPtpProfile', + 'mbgSyncboxN2XPtpNwProt', + 'mbgSyncboxN2XPtpPortState', + 'mbgSyncboxN2XPtpDelayMechanism', + 'mbgSyncboxN2XPtpDelayRequestInterval', + 'mbgSyncboxN2XPtpTimescale', + 'mbgSyncboxN2XPtpUTCOffset', + 'mbgSyncboxN2XPtpLeapSecondAnnounced', + 'mbgSyncboxN2XPtpGrandmasterClockID', + 'mbgSyncboxN2XPtpGrandmasterTimesource', + 'mbgSyncboxN2XPtpGrandmasterPriority1', + 'mbgSyncboxN2XPtpGrandmasterClockClass', + 'mbgSyncboxN2XPtpGrandmasterClockAccuracy', + 'mbgSyncboxN2XPtpGrandmasterClockVariance', + 'mbgSyncboxN2XPtpOffsetToGrandmaster', + 'mbgSyncboxN2XPtpMeanPathDelay'] + output_oids = ['mbgSyncboxN2XOutputMode'] + mib = 'MBG-SYNCBOX-N2X-MIB' + + self.get_list = [] + + # Create the lists of OIDs to send get commands + for oid in oids: + self.get_list.append([(mib, oid, 0)]) + + for out in outputs: + for oid in output_oids: + self.get_list.append([(mib, oid, out-1)]) agg_params = { 'frame_length': 10 * 60 # [sec] @@ -381,30 +392,21 @@ def acq(self, session, params=None): if (read_time - self.lastGet) < self.sample_period: continue - get_list = [] - - # Create the lists of OIDs to send get commands - for oid in self.oids: - get_list.append([(self.mib, oid, 0)]) - - outputs = 3 # number of outputs on the syncbox - for i in range(outputs): - for oid in self.output_oids: - get_list.append([(self.mib, oid, i)]) - # Issue SNMP GET command # The syncbox has a unique case this requires issuing GET commands # one by one or else it will return the same data for each OID - get_result = yield self.snmp.get(get_list[0], self.version) - for get in get_list[1:]: + get_result = [] + for get in self.get_list: result = yield self.snmp.get(get, self.version) if result is None: self.connected = False session.data['syncbox_connection'] = {'last_attempt': time.time(), 'connected': False} - continue + break get_result.extend(result) self.connected = True + if not self.connected: + continue # Do not publish if syncbox connection has dropped try: @@ -456,6 +458,8 @@ def add_agent_args(parser=None): help="SNMP version for communication. Must match " + "configuration on the syncbox.") pgroup.add_argument("--mode", default='acq', choices=['acq', 'test']) + pgroup.add_argument("--outputs", nargs='+', default=[1,2,3], type=int, + help="Syncbox outputs to monitor. Defaults to [1,2,3].") return parser @@ -478,7 +482,8 @@ def main(args=None): p = MeinbergSyncboxAgent(agent, address=args.address, port=int(args.port), - version=int(args.snmp_version)) + version=int(args.snmp_version), + outputs=args.outputs) agent.register_process("acq", p.acq, From 8e0219325429ad68aceee977ced8a0b96e8d151a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:41:48 +0000 Subject: [PATCH 11/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- simulators/lakeshore240/ls240_simulator.py | 2 +- socs/agents/acu/agent.py | 2 +- socs/agents/hwp_gripper/agent.py | 6 +++--- socs/agents/lakeshore372/agent.py | 2 +- socs/agents/meinberg_syncbox/agent.py | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/simulators/lakeshore240/ls240_simulator.py b/simulators/lakeshore240/ls240_simulator.py index 43df44942..4411c1d6a 100644 --- a/simulators/lakeshore240/ls240_simulator.py +++ b/simulators/lakeshore240/ls240_simulator.py @@ -96,7 +96,7 @@ def run(self): else: raise e else: - print(f"Could not connect to ports in {range(self.port, self.port+5)}") + print(f"Could not connect to ports in {range(self.port, self.port + 5)}") sock.listen(1) diff --git a/socs/agents/acu/agent.py b/socs/agents/acu/agent.py index 5dea06bdc..9c7f0454c 100644 --- a/socs/agents/acu/agent.py +++ b/socs/agents/acu/agent.py @@ -1180,7 +1180,7 @@ def get_history(t): if state != last_state: _state = f'{axis}.state={state.name}' self.log.info( - f'{_state:<30} dt={now-start_time:7.3f} dist={distance:8.3f}') + f'{_state:<30} dt={now - start_time:7.3f} dist={distance:8.3f}') last_state = state # Handle task abort diff --git a/socs/agents/hwp_gripper/agent.py b/socs/agents/hwp_gripper/agent.py index f43724ec9..072d6dbf2 100644 --- a/socs/agents/hwp_gripper/agent.py +++ b/socs/agents/hwp_gripper/agent.py @@ -769,16 +769,16 @@ def monitor_supervisor(self, session, params=None): time_since_ok = time.time() - last_ok_time if time_since_ok > params['no_data_warn_time'] and not warning_issued: self.log.error( - f"Have not received 'ok' in {time_since_ok/60:.2f} minutes." + f"Have not received 'ok' in {time_since_ok / 60:.2f} minutes." f"Will issue shutdown in " - f"{params['no_data_shutdown_time']/60:.2f} minutes." + f"{params['no_data_shutdown_time'] / 60:.2f} minutes." ) warning_issued = True if time_since_ok > params['no_data_shutdown_time']: self.log.error( f"Have not received 'ok' in " - f"{params['no_data_shutdown_time']/60:.2f} minutes. " + f"{params['no_data_shutdown_time'] / 60:.2f} minutes. " "Issuing shutdown" ) self.agent.start('shutdown') diff --git a/socs/agents/lakeshore372/agent.py b/socs/agents/lakeshore372/agent.py index 5ed51aa5d..04275309f 100644 --- a/socs/agents/lakeshore372/agent.py +++ b/socs/agents/lakeshore372/agent.py @@ -924,7 +924,7 @@ def get_input_setup(self, session, params): "units": ls_chann_setting.units} return True, f"Channel {channel} has measurement inputs {input_setup} = [mode," \ - "excitation, auto range, range, cs_shunt, units]" + "excitation, auto range, range, cs_shunt, units]" @ocs_agent.param('setpoint', type=float) @ocs_agent.param('heater', type=str) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index 994a64d7c..fe32ffd5e 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -173,7 +173,7 @@ class MeinbergSyncboxAgent: txaio logger object, created by the OCSAgent """ - def __init__(self, agent, address, port=161, version=1, outputs=[1,2,3]): + def __init__(self, agent, address, port=161, version=1, outputs=[1, 2, 3]): self.agent = agent self.is_streaming = False self.log = self.agent.log @@ -220,7 +220,7 @@ def __init__(self, agent, address, port=161, version=1, outputs=[1,2,3]): for out in outputs: for oid in output_oids: - self.get_list.append([(mib, oid, out-1)]) + self.get_list.append([(mib, oid, out - 1)]) agg_params = { 'frame_length': 10 * 60 # [sec] @@ -458,7 +458,7 @@ def add_agent_args(parser=None): help="SNMP version for communication. Must match " + "configuration on the syncbox.") pgroup.add_argument("--mode", default='acq', choices=['acq', 'test']) - pgroup.add_argument("--outputs", nargs='+', default=[1,2,3], type=int, + pgroup.add_argument("--outputs", nargs='+', default=[1, 2, 3], type=int, help="Syncbox outputs to monitor. Defaults to [1,2,3].") return parser From 6e9761dc0e3447890403a491e45e1d4de239f40d Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Thu, 25 Apr 2024 19:21:38 +0000 Subject: [PATCH 12/13] set session degraded --- socs/agents/meinberg_syncbox/agent.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/socs/agents/meinberg_syncbox/agent.py b/socs/agents/meinberg_syncbox/agent.py index fe32ffd5e..7be229b4b 100644 --- a/socs/agents/meinberg_syncbox/agent.py +++ b/socs/agents/meinberg_syncbox/agent.py @@ -406,7 +406,9 @@ def acq(self, session, params=None): get_result.extend(result) self.connected = True if not self.connected: + session.degraded = True continue + session.degraded = False # Do not publish if syncbox connection has dropped try: From a8b3860f63be891754da10e95e35916c1f466eac Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Thu, 25 Apr 2024 19:31:25 +0000 Subject: [PATCH 13/13] update docs --- docs/agents/meinberg_syncbox_agent.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/agents/meinberg_syncbox_agent.rst b/docs/agents/meinberg_syncbox_agent.rst index b4c7e4b43..04b5dd7cc 100644 --- a/docs/agents/meinberg_syncbox_agent.rst +++ b/docs/agents/meinberg_syncbox_agent.rst @@ -31,11 +31,13 @@ using all of the available arguments:: 'arguments': [['--address', '192.168.2.166'], ['--port', 161], ['--mode', 'acq'], - ['--snmp-version', 1]]}, + ['--snmp-version', 1], + ['--outputs', [1, 2, 3]]]}, .. note:: The ``--address`` argument should be the address of the syncbox on the network. This is not the main Meinberg M1000 device. + The ``--outputs`` argument can be any of the available 3 outputs. Docker Compose ``````````````