#!/usr/bin/env python # -*- coding: utf-8 -*- # # AWL simulator - LinuxCNC HAL module # # Copyright 2013-2014 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 from awlsim.common.compat import * import sys import os import time import getopt from awlsim.common import * from awlsim.coreserver import * class LinuxCNC_NotRunning(Exception): pass # 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"): try: os.stat(lockname) return True except OSError: pass 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() # Create the LinuxCNC HAL pins def createHalPins(hal, inputBase, inputSize, outputBase, outputSize): HAL_BIT, HAL_U32, HAL_S32, HAL_FLOAT = \ LinuxCNC_HAL.HAL_BIT, LinuxCNC_HAL.HAL_U32, \ LinuxCNC_HAL.HAL_S32, LinuxCNC_HAL.HAL_FLOAT HAL_IN, HAL_OUT, HAL_RO, HAL_RW = \ LinuxCNC_HAL.HAL_IN, LinuxCNC_HAL.HAL_OUT, \ LinuxCNC_HAL.HAL_RO, LinuxCNC_HAL.HAL_RW # Create the input pins for i in range(inputBase, inputBase + inputSize): offset = i - inputBase for bit in range(8): hal.newpin("input.bit.%d.%d" % (i, bit), HAL_BIT, HAL_IN) hal.newparam("input.bit.%d.%d.active" % (i, bit), HAL_BIT, HAL_RW) hal.newpin("input.u8.%d" % i, HAL_U32, HAL_IN) hal.newparam("input.u8.%d.active" % i, HAL_BIT, HAL_RW) if i % 2: continue if inputSize - offset < 2: continue hal.newpin("input.u16.%d" % i, HAL_U32, HAL_IN) hal.newparam("input.u16.%d.active" % i, HAL_BIT, HAL_RW) hal.newpin("input.s16.%d" % i, HAL_S32, HAL_IN) hal.newparam("input.s16.%d.active" % i, HAL_BIT, HAL_RW) if inputSize - offset < 4: continue hal.newpin("input.u31.%d" % i, HAL_U32, HAL_IN) hal.newparam("input.u31.%d.active" % i, HAL_BIT, HAL_RW) hal.newpin("input.s32.%d" % i, HAL_S32, HAL_IN) hal.newparam("input.s32.%d.active" % i, HAL_BIT, HAL_RW) hal.newpin("input.float.%d" % i, HAL_FLOAT, HAL_IN) hal.newparam("input.float.%d.active" % i, HAL_BIT, HAL_RW) # Create the output pins for i in range(outputBase, outputBase + outputSize): offset = i - outputBase for bit in range(8): hal.newpin("output.bit.%d.%d" % (i, bit), HAL_BIT, HAL_OUT) hal.newparam("output.bit.%d.%d.active" % (i, bit), HAL_BIT, HAL_RW) hal.newpin("output.u8.%d" % i, HAL_U32, HAL_OUT) hal.newparam("output.u8.%d.active" % i, HAL_BIT, HAL_RW) if i % 2: continue if outputSize - offset < 2: continue hal.newpin("output.u16.%d" % i, HAL_U32, HAL_OUT) hal.newparam("output.u16.%d.active" % i, HAL_BIT, HAL_RW) hal.newpin("output.s16.%d" % i, HAL_S32, HAL_OUT) hal.newparam("output.s16.%d.active" % i, HAL_BIT, HAL_RW) if outputSize - offset < 4: continue hal.newpin("output.u31.%d" % i, HAL_U32, HAL_OUT) hal.newparam("output.u31.%d.active" % i, HAL_BIT, HAL_RW) hal.newpin("output.s32.%d" % i, HAL_S32, HAL_OUT) hal.newparam("output.s32.%d.active" % i, HAL_BIT, HAL_RW) hal.newpin("output.float.%d" % i, HAL_FLOAT, HAL_OUT) hal.newparam("output.float.%d.active" % i, HAL_BIT, HAL_RW) hal.newparam("config.ready", HAL_BIT, HAL_RW) def loadHardwareModule(server, hal, inputBase, outputBase, inputSize, outputSize): # Load the hardware interface layer parameters = { "hal" : hal, "removeOnReset" : "0", "inputAddressBase" : str(inputBase), "outputAddressBase" : str(outputBase), "inputSize" : str(inputSize), "outputSize" : str(outputSize), } server.loadHardwareModule("linuxcnc", parameters) def usage(): print("awlsim-linuxcnc-hal version %s" % VERSION_STRING) print("") print("Usage: awlsim-linuxcnc-hal [OPTIONS] PROJECT.awlpro") print("") print("Options:") print(" -i|--input-size SIZE The input area size, in bytes.") print(" Default: 32") print(" -I|--input-base BASE The AWL/STL input address base.") print(" Default: 0") print(" -o|--output-size SIZE The output area size, in bytes.") print(" Default: 32") print(" -O|--output-base BASE The AWL/STL output address base.") print(" Default: 0") 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(" awlsimhw_linuxcnc/awlsim.hal") def main(): global LinuxCNC_HAL 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 opt_inputSize = 32 opt_inputBase = 0 opt_outputSize = 32 opt_outputBase = 0 opt_listen = (AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT) opt_loglevel = Logging.LOG_INFO opt_nice = None opt_watchdog = True opt_maxRuntime = -1.0 opt_extInsns = None try: (opts, args) = getopt.getopt(sys.argv[1:], "hi:I:o:O:l:L:N:W:M:x", [ "help", "input-size=", "input-base=", "output-size=", "output-base=", "listen=", "loglevel=", "nice=", "watchdog=", "max-runtime=", "extended-insns", ]) 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: idx = v.rfind(":") if idx <= 0: raise ValueError opt_listen = (v[:idx], int(v[idx+1:])) except ValueError: 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 len(args) != 1: usage() return 1 projectFile = args[0] result = 0 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 # Create the LinuxCNC HAL component. hal = LinuxCNC_HAL.component("awlsim") createHalPins(hal, opt_inputBase, opt_inputSize, opt_outputBase, opt_outputSize) # Read the project. project = Project.fromProjectOrRawAwlFile(projectFile) # Configure the core and the core server. server = AwlSimServer() loadHardwareModule(server = server, hal = hal, inputBase = opt_inputBase, outputBase = opt_outputBase, inputSize = opt_inputSize, outputSize = opt_outputSize) server.cpuEnableObTempPresets(project.getObTempPresetsEn()) server.cpuEnableExtendedInsns(project.getExtInsnsEn() or\ opt_extInsns) #TODO set cycle time limit server.cpuSetRunTimeLimit(opt_maxRuntime) server.cpuSetSpecs(project.getCpuSpecs()) # Load the program for symSrc in project.getSymTabSources(): server.loadSymTabSource(symSrc) for libSel in project.getLibSelections(): server.loadLibraryBlock(libSel) for awlSrc in project.getAwlSources(): server.loadAwlSource(awlSrc) # Set the Linux-CNC watchdog as cycle exit hook. server.setCycleExitHook(watchdogHook) # Run the server printInfo("Starting interpreter core...") server.startup(host = opt_listen[0], port = opt_listen[1], handleExceptionServerside = True, handleMaintenanceServerside = True) server.setRunState(server.STATE_RUN) lastExceptionTime = None while True: try: server.run() except (AwlParserError, AwlSimError) as e: now = time.time() if lastExceptionTime is not None and\ now - lastExceptionTime < 1.0: # The last fault is less than one second # in the past. Raise a fatal exception. printError("Fatal fault detected") raise e else: lastExceptionTime = now # 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 = 1 except KeyboardInterrupt as e: result = 1 except (AwlParserError, AwlSimError) as e: printError(e.getReport()) result = 1 except MaintenanceRequest as e: if e.requestType in (MaintenanceRequest.TYPE_SHUTDOWN, MaintenanceRequest.TYPE_STOP, MaintenanceRequest.TYPE_RTTIMEOUT): result = 0 else: printError("Received invalid maintenance request %d" %\ e.requestType) result = 1 printInfo("LinuxCNC HAL module shutdown.") return result if __name__ == "__main__": sys.exit(main())