#############################################
# Copyright 2023-present ifm electronic, gmbh
# SPDX-License-Identifier: Apache-2.0
#############################################
import json
import logging
import time
from datetime import datetime
from ifm3dpy.device import O3R
from ifm3dpy.device import Error as ifm3dpy_error
from ifm3dpy.framegrabber import FrameGrabber
class O3RDiagnostic:
"""Helper functions for retrieving diagnostics when requested
or asynchronously."""
def __init__(self, o3r: O3R, log_to_file: bool):
self._o3r = o3r
self._fg = None
self.diagnostic = []
###########################
# Logger configuration:
self.logger = logging.getLogger(__name__)
_log_format = "%(asctime)s:%(filename)-10s:%(levelname)-8s:%(message)s"
_log_datefmt = "%y-%m-%d %H:%M:%S"
if log_to_file:
logging.basicConfig(
filename=f'{datetime.now().strftime("%Y%m%d")}_{datetime.now().strftime("%H%M%S")}_diagnostic.log',
format=_log_format,
datefmt=_log_datefmt,
)
else:
logging.basicConfig(format=_log_format, datefmt=_log_datefmt)
self.logger.setLevel(logging.INFO)
###########################
def get_diagnostic_filtered(self, filter_mask: json = {}) -> dict:
"""If the filter is set to {}, all diagnostic will be retrieved.
:param filter_mask (json): the filter mask defining which error messages will be retrieved.
For example, setting to {"state": "active"} will retrieve all active errors.
If the filter is set to {}, all diagnostic will be retrieved.
:return: the diagnostic, filtered with the filter_mask
"""
try:
self.logger.info(f"Poll O3R diagnostic data with filter {filter_mask}.")
return self._o3r.get_diagnostic_filtered(filter_mask)
except ifm3dpy_error as err:
self.logger.exception("Error when getting diagnostic data.")
raise err
def _async_diag_callback(self, id: int, message: str):
"""Callback to log active errors.
:param id (int): ID of Error Message
:param message (str): Whole Diagnostic Information
"""
self.logger.error(
"Received diagnostic message via callback with id=%s, content=%s",
id,
message,
)
self.diagnostic = {"id": id, "message": message}
# Here the user should add custom error handling.
def start_async_diag(self):
"""Start the diagnostic asynchronous stream.
The _async_diag_callback function will be called for any error received.
"""
self._fg = FrameGrabber(self._o3r, 50009)
self._fg.on_async_error(callback=self._async_diag_callback)
self.logger.info(
"Starting async diagnostic monitoring. \nErrors ids and descriptions will be logged."
)
self._fg.start([])
def stop_async_diag(self):
"""Stops the Framegrabber listening to the diagnostic information"""
self.logger.info("Stopping async diagnostic monitoring.")
self._fg.stop()
def main(IP="192.168.0.69", log_to_file=False):
#############################
# Configure the objects.
# Make sure to edit for your
# IP address.
#############################
o3r = O3R(IP)
o3r_diagnostic = O3RDiagnostic(o3r, log_to_file)
#############################
# Requesting diagnostic data
#############################
o3r_diagnostic.logger.info(
f"Currently active errors: {o3r_diagnostic.get_diagnostic_filtered({'state': 'active'})}"
)
#############################
# Starting the asynchronous
# diagnostic monitoring. Any
# new error will be logged.
# Note that this function should
# be customized to handle specific
# error handling in the user
# application
#############################
o3r_diagnostic.start_async_diag()
while True:
try:
time.sleep(0.1)
except KeyboardInterrupt:
o3r_diagnostic.stop_async_diag()
o3r_diagnostic.logger.info(
"You reached the end of the O3R diagnostic tutorial! "
)
break
if __name__ == "__main__":
try:
# If the example python package was build, import the configuration
from ovp8xxexamples import config
IP = config.IP
log_to_file = config.LOG_TO_FILE
except ImportError:
# Otherwise, use default values
print(
"Unable to import the configuration.\nPlease run 'pip install -e .' from the python root directory"
)
print("Defaulting to the default configuration.")
IP = "192.168.0.69"
log_to_file=False
main(IP=IP, log_to_file=log_to_file)