123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- #
- # AWL simulator - LinuxCNC HAL module
- #
- # Copyright 2013-2020 Michael Buesch <m@bues.ch>
- #
- # 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())
|