#!/usr/bin/env python # -*- coding: utf-8 -*- # # AWL simulator - LinuxCNC HAL module # # Copyright 2013-2020 Michael Buesch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # from __future__ import division, absolute_import, print_function, unicode_literals import sys import os import getopt from awlsim_loader.common import * from awlsim_loader.core import * from awlsim_loader.coreserver import * from awlsim_loader.coreclient import * from awlsim.common.monotonic import monotonic_time class LinuxCNC_NotRunning(Exception): pass def linuxCNCTriggerEStop(): # Try to trigger LinuxCNC emergency stop. global linuxcnc_mod if linuxcnc_mod is not None: try: cmd = linuxcnc_mod.command() cmd.state(linuxcnc_mod.STATE_ESTOP) cmd.wait_complete() except Exception as e: print("ERROR: Failed to trigger LinuxCNC E-Stop: %s" % str(e), file=sys.stderr) else: print("Triggered LinuxCNC E-Stop") # Check presence of LinuxCNC. # Returns normally, if LinuxCNC is detected. # Raises LinuxCNC_NotRunning, if LinuxCNC is not detected. def watchdogHook(_unused = None): # Check whether LinuxCNC is running. for lockname in ("/tmp/linuxcnc.lock", "/tmp/emc.lock"): if os.path.exists(lockname): return True if not opt_watchdog: # The check is disabled. Return success. return True printError("LinuxCNC doesn't seem to be running. "\ "(Use '--watchdog off' to disable this check.)") raise LinuxCNC_NotRunning() def usage(): print("awlsim-linuxcnc-hal version %s" % VERSION_STRING) print("") print("Usage: awlsim-linuxcnc-hal [OPTIONS] PROJECT.awlpro") print("") print("Options:") print(" -w|--rw-project Enable project file writing after download of new program.") print(" Default: Do not write to the project file.") print("") print(" -i|--input-size SIZE The input area size, in bytes.") print(" Overrides input-size from project file.") print(" -I|--input-base BASE The AWL/STL input address base.") print(" Overrides input-base from project file.") print(" -o|--output-size SIZE The output area size, in bytes.") print(" Overrides output-size from project file.") print(" -O|--output-base BASE The AWL/STL output address base.") print(" Overrides output-base from project file.") print("") print(" -l|--listen HOST:PORT Set the address and port where the") print(" awlsim core server should listen on.") print(" Defaults to %s:%d" %\ (AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT)) print("") print(" -L|--loglevel LVL Set the log level:") print(" 0: Log nothing") print(" 1: Log errors") print(" 2: Log errors and warnings") print(" 3: Log errors, warnings and info messages (default)") print(" 4: Verbose logging") print(" 5: Extremely verbose logging") print(" -N|--nice NICE Renice the process. -20 <= NICE <= 19.") print(" Default: Do not renice") print("") print("Debugging options:") print(" -W|--watchdog 1/0 Enable/disable LinuxCNC runtime watchdog.") print(" Default: on") print(" -M|--max-runtime SEC Module will be stopped after SEC seconds.") print(" Default: No timeout") print(" -x|--extended-insns Force-enable extended instructions") print("") print("For an example LinuxCNC HAL configuration see:") print(" examples/linuxcnc-demo/linuxcnc-demo.hal") def main(): global linuxcnc_mod linuxcnc_mod = None global opt_inputSize global opt_inputBase global opt_outputSize global opt_outputBase global opt_listen global opt_loglevel global opt_nice global opt_watchdog global opt_maxRuntime global opt_extInsns global opt_rwProject opt_inputSize = None opt_inputBase = None opt_outputSize = None opt_outputBase = None opt_listen = (AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT) opt_loglevel = Logging.LOG_INFO opt_nice = None opt_watchdog = True opt_maxRuntime = None opt_extInsns = None opt_rwProject = False try: (opts, args) = getopt.getopt(sys.argv[1:], "hi:I:o:O:l:L:N:W:M:xw", [ "help", "input-size=", "input-base=", "output-size=", "output-base=", "listen=", "loglevel=", "nice=", "watchdog=", "max-runtime=", "extended-insns", "rw-project", ]) except getopt.GetoptError as e: printError(str(e)) usage() return 1 for (o, v) in opts: if o in ("-h", "--help"): usage() return 0 if o in ("-i", "--input-size"): try: opt_inputSize = int(v) except ValueError: printError("-i|--input-size: Invalid argument") return 1 if o in ("-I", "--input-base"): try: opt_inputBase = int(v) except ValueError: printError("-I|--input-base: Invalid argument") return 1 if o in ("-o", "--output-size"): try: opt_outputSize = int(v) except ValueError: printError("-o|--output-size: Invalid argument") return 1 if o in ("-O", "--output-base"): try: opt_outputBase = int(v) except ValueError: printError("-O|--output-base: Invalid argument") return 1 if o in ("-l", "--listen"): try: host, port = parseNetAddress(v) if not host.strip() or\ host in {"any", "all"}: host = "" if port is None: port = AwlSimServer.DEFAULT_PORT opt_listen = (host, port) except AwlSimError as e: printError("-l|--listen: Invalid host/port") return 1 if o in ("-L", "--loglevel"): try: opt_loglevel = int(v) except ValueError: printError("-L|--loglevel: Invalid log level") return 1 if o in ("-N", "--nice"): try: opt_nice = int(v) if opt_nice < -20 or opt_nice > 19: raise ValueError except ValueError: printError("-N|--nice: Invalid niceness level") return 1 if o in ("-W", "--watchdog"): opt_watchdog = str2bool(v) if o in ("-M", "--max-runtime"): try: opt_maxRuntime = float(v) except ValueError: printError("-M|--max-runtime: Invalid time format") return 1 if o in ("-x", "--extended-insns"): opt_extInsns = True if o in ("-w", "--rw-project"): opt_rwProject = True if len(args) != 1: usage() return 1 projectFile = args[0] result = ExitCodes.EXIT_OK server = None try: Logging.setPrefix("awlsim-linuxcnc: ") Logging.setLoglevel(opt_loglevel) # Adjust process priority if opt_nice is not None: try: os.nice(opt_nice) except OSError as e: printError("Failed to renice process to " "%d: %s" % (opt_nice, str(e))) return 1 # Try to import the LinuxCNC HAL module try: import hal as LinuxCNC_HAL except ImportError as e: printError("Failed to import LinuxCNC HAL " "module: %s" % str(e)) return 1 try: import linuxcnc as linuxcnc_mod except ImportError as e: printError("Failed to import LinuxCNC " "module: %s" % str(e)) return 1 # Create the LinuxCNC HAL component. halComponent = LinuxCNC_HAL.component("awlsim") # Read the project. project = Project.fromProjectOrRawAwlFile(projectFile) hwmodSettings = project.getHwmodSettings() # Get the 'linuxcnc' hardware module descriptor. linuxcncHwmodDesc = None for modDesc in hwmodSettings.getLoadedModules(): if modDesc.getModuleName() == "linuxcnc": if linuxcncHwmodDesc: printError("ERROR: More than one 'linuxcnc' hardware " "module found in the project file. Only " "one 'linuxcnc' module is supported.") linuxcncHwmodDesc = modDesc if not linuxcncHwmodDesc: if opt_inputBase is None and\ opt_outputBase is None and\ opt_inputSize is None and\ opt_outputSize is None: printWarning("Warning: Hardware module 'linuxcnc' not " "included in project file. " "Loading module nevertheless.") modDesc = HwmodDescriptor(moduleName = "linuxcnc", parameters = { "inputAddressBase" : "0", "outputAddressBase" : "0", "inputSize" : "32", "outputSize" : "32", }) hwmodSettings.addLoadedModule(modDesc) linuxcncHwmodDesc = modDesc # Override hardware module parameters, if required. if opt_inputBase is not None: linuxcncHwmodDesc.setParameterValue("inputAddressBase", str(opt_inputBase)) if opt_inputSize is not None: linuxcncHwmodDesc.setParameterValue("inputSize", str(opt_inputSize)) if opt_outputBase is not None: linuxcncHwmodDesc.setParameterValue("outputAddressBase", str(opt_outputBase)) if opt_outputSize is not None: linuxcncHwmodDesc.setParameterValue("outputSize", str(opt_outputSize)) # Override CPUConf, if required. conf = project.getCpuConf() if opt_maxRuntime is not None: conf.setRunTimeLimitUs(int(round(opt_maxRuntime * 1000000.0))) if opt_extInsns is not None: conf.setExtInsnsEn(opt_extInsns) # Pass the hal component singleton to the Awlsim hw module. loader = HwModLoader.loadModule("linuxcnc") mod = loader.getModule() mod.setLinuxCNCHalComponentSingleton(LinuxCNC_HAL, halComponent) server = AwlSimServer() server.setCycleExitHook(watchdogHook) printInfo("Starting core server...") server.startup(host=opt_listen[0], port=opt_listen[1], project=project, projectWriteBack=opt_rwProject, raiseExceptionsFromRun=True, handleMaintenanceServerside=True) try: server.setRunState(server.STATE_RUN) except AwlSimError as e: printError("Failed to go to RUN state.") # Continue to main loop. lastExceptionTime = monotonic_time() - 1.0 exceptionCount = 0 while True: try: server.run() except (AwlParserError, AwlSimError) as e: linuxCNCTriggerEStop() now = monotonic_time() if now - lastExceptionTime < 1.0: # The last fault is less than one second ago. exceptionCount += 1 else: exceptionCount = 0 lastExceptionTime = now if exceptionCount >= 10: # Many exceptions happened in a row. # Raise a fatal exception. printError("Fatal fault detected") raise e else: # Non-fatal fault. # Ensure the CPU is stopped and enter # the run loop again. printError(e.getReport()) printError("CPU stopped due to fault") server.setRunState(server.STATE_STOP) continue # Run loop exited normally. Bail out. break except LinuxCNC_NotRunning as e: result = ExitCodes.EXIT_ERR_OTHER except KeyboardInterrupt as e: result = ExitCodes.EXIT_ERR_OTHER except (AwlParserError, AwlSimError) as e: printError(e.getReport()) result = ExitCodes.EXIT_ERR_SIM except MaintenanceRequest as e: if e.requestType in (MaintenanceRequest.TYPE_SHUTDOWN, MaintenanceRequest.TYPE_STOP, MaintenanceRequest.TYPE_RTTIMEOUT): result = ExitCodes.EXIT_OK else: printError("Received invalid maintenance request %d" %\ e.requestType) result = ExitCodes.EXIT_ERR_SIM finally: linuxCNCTriggerEStop() if server: server.shutdown() printInfo("LinuxCNC HAL module shutdown.") return result if __name__ == "__main__": sys.exit(main())