123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- #
- # PROFIBUS DP - LinuxCNC HAL module
- #
- # Copyright 2016-2023 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 time
- import getopt
- import struct
- from pyprofibus import ProfibusError, FdlError, DpError
- from pyprofibus.compat import isPy2Compat
- from pyprofibus.util import fileExists, FaultDebouncer
- from pyprofibus.dp import DpTelegram_SetPrm_Req
- from pyprofibus.conf import PbConf, PbConfError
- from pyprofibus.version import *
- class SigBit:
- def __init__(self, hal, halName, byteOffset, bitOffset):
- self.hal = hal
- self.halName = halName
- self.byteOffset = byteOffset
- self.bitOffset = bitOffset
- self.setMask = 1 << bitOffset
- self.clrMask = ~(1 << bitOffset)
- def fromHal(self, destBuf):
- if self.hal[self.halName]:
- destBuf[self.byteOffset] |= self.setMask
- else:
- destBuf[self.byteOffset] &= self.clrMask
- def toHal(self, srcBuf):
- self.hal[self.halName] = (srcBuf[self.byteOffset] >> self.bitOffset) & 1
- def __str__(self):
- return "profibus.%s" % self.halName
- class SigU8:
- def __init__(self, hal, halName, offset):
- self.hal = hal
- self.halName = halName
- self.offset = offset
- def fromHal(self, destBuf):
- destBuf[self.offset] = self.hal[self.halName] & 0xFF
- def toHal(self, srcBuf):
- self.hal[self.halName] = srcBuf[self.offset] & 0xFF
- def __str__(self):
- return "profibus.%s" % self.halName
- class SigU16:
- def __init__(self, hal, halName, offset):
- self.hal = hal
- self.halName = halName
- self.offset = offset
- def fromHal(self, destBuf):
- word = self.hal[self.halName] & 0xFFFF
- destBuf[self.offset] = (word >> 8) & 0xFF
- destBuf[self.offset + 1] = word & 0xFF
- def toHal(self, srcBuf):
- word = (srcBuf[self.offset] << 8) |\
- srcBuf[self.offset + 1]
- self.hal[self.halName] = word & 0xFFFF
- def __str__(self):
- return "profibus.%s" % self.halName
- class SigS16:
- def __init__(self, hal, halName, offset):
- self.hal = hal
- self.halName = halName
- self.offset = offset
- def fromHal(self, destBuf):
- word = self.hal[self.halName] & 0xFFFF
- destBuf[self.offset] = (word >> 8) & 0xFF
- destBuf[self.offset + 1] = word & 0xFF
- def toHal(self, srcBuf):
- word = (srcBuf[self.offset] << 8) |\
- srcBuf[self.offset + 1]
- if word & 0x8000:
- self.hal[self.halName] = -((~word + 1) & 0xFFFF)
- else:
- self.hal[self.halName] = word & 0xFFFF
- def __str__(self):
- return "profibus.%s" % self.halName
- class SigU31:
- def __init__(self, hal, halName, offset):
- self.hal = hal
- self.halName = halName
- self.offset = offset
- def fromHal(self, destBuf):
- dword = self.hal[self.halName] & 0x7FFFFFFF
- destBuf[self.offset] = (dword >> 24) & 0xFF
- destBuf[self.offset + 1] = (dword >> 16) & 0xFF
- destBuf[self.offset + 2] = (dword >> 8) & 0xFF
- destBuf[self.offset + 3] = dword & 0xFF
- def toHal(self, srcBuf):
- dword = (srcBuf[self.offset] << 24) |\
- (srcBuf[self.offset + 1] << 16) |\
- (srcBuf[self.offset + 2] << 8) |\
- srcBuf[self.offset + 3]
- self.hal[self.halName] = dword & 0x7FFFFFFF
- def __str__(self):
- return "profibus.%s" % self.halName
- class SigS32:
- def __init__(self, hal, halName, offset):
- self.hal = hal
- self.halName = halName
- self.offset = offset
- def fromHal(self, destBuf):
- dword = self.hal[self.halName] & 0xFFFFFFFF
- destBuf[self.offset] = (dword >> 24) & 0xFF
- destBuf[self.offset + 1] = (dword >> 16) & 0xFF
- destBuf[self.offset + 2] = (dword >> 8) & 0xFF
- destBuf[self.offset + 3] = dword & 0xFF
- def toHal(self, srcBuf):
- dword = (srcBuf[self.offset] << 24) |\
- (srcBuf[self.offset + 1] << 16) |\
- (srcBuf[self.offset + 2] << 8) |\
- srcBuf[self.offset + 3]
- if dword & 0x80000000:
- self.hal[self.halName] = -((~dword + 1) & 0xFFFFFFFF)
- else:
- self.hal[self.halName] = dword & 0xFFFFFFFF
- def __str__(self):
- return "profibus.%s" % self.halName
- class SigFloat:
- floatStruct = struct.Struct(str('>f'))
- def __init__(self, hal, halName, offset):
- self.hal = hal
- self.halName = halName
- self.offset = offset
- def fromHal(self, destBuf):
- buf = self.floatStruct.pack(self.hal[self.halName])
- if isPy2Compat:
- buf = [ ord(b) for b in buf ]
- destBuf[self.offset : self.offset + 4] = buf[0 : 4]
- def toHal(self, srcBuf):
- dword = (srcBuf[self.offset] << 24) |\
- (srcBuf[self.offset + 1] << 16) |\
- (srcBuf[self.offset + 2] << 8) |\
- srcBuf[self.offset + 3]
- if isPy2Compat:
- value = self.floatStruct.unpack(
- chr((dword >> 24) & 0xFF) +\
- chr((dword >> 16) & 0xFF) +\
- chr((dword >> 8) & 0xFF) +\
- chr(dword & 0xFF)
- )[0]
- else:
- value = self.floatStruct.unpack(
- bytes( ((dword >> 24) & 0xFF,
- (dword >> 16) & 0xFF,
- (dword >> 8) & 0xFF,
- dword & 0xFF)
- )
- )[0]
- self.hal[self.halName] = value
- def __str__(self):
- return "profibus.%s" % self.halName
- class Worker:
- def __init__(self, hal, master):
- self.__configDone = False
- self.hal = hal
- self.master = master
- self.slaves = master.getSlaveList()
- def __buildTable(self, slaveAddr, direction, size):
- tab = []
- for i in range(0, size):
- for bitNr in range(8):
- halName = "slave.%d.%s.bit.%d.%d" % (
- slaveAddr, direction, i, bitNr)
- if self.hal[halName + ".active"]:
- tab.append(SigBit(self.hal, halName,
- i, bitNr))
- halName = "slave.%d.%s.u8.%d" % (
- slaveAddr, direction, i)
- if self.hal[halName + ".active"]:
- tab.append(SigU8(self.hal, halName, i))
- if i % 2:
- continue
- if size - i < 2:
- continue
- halName = "slave.%d.%s.u16.%d" % (
- slaveAddr, direction, i)
- if self.hal[halName + ".active"]:
- tab.append(SigU16(self.hal, halName, i))
- halName = "slave.%d.%s.s16.%d" % (
- slaveAddr, direction, i)
- if self.hal[halName + ".active"]:
- tab.append(SigS16(self.hal, halName, i))
- if size - i < 4:
- continue
- halName = "slave.%d.%s.u31.%d" % (
- slaveAddr, direction, i)
- if self.hal[halName + ".active"]:
- tab.append(SigU31(self.hal, halName, i))
- halName = "slave.%d.%s.s32.%d" % (
- slaveAddr, direction, i)
- if self.hal[halName + ".active"]:
- tab.append(SigS32(self.hal, halName, i))
- halName = "slave.%d.%s.float.%d" % (
- slaveAddr, direction, i)
- if self.hal[halName + ".active"]:
- tab.append(SigFloat(self.hal, halName, i))
- return tab
- def __tryBuildConfig(self):
- if not self.hal["config.ready"]:
- return
- for slave in self.slaves:
- slaveConf = slave.slaveConf
- if slaveConf is None:
- continue
- activePbMasterOutputs = self.__buildTable(
- slave.slaveAddr, "mosi", slaveConf.inputSize)
- activePbMasterInputs = self.__buildTable(
- slave.slaveAddr, "miso", slaveConf.outputSize)
- slave.userData["activePbMasterInputs"] = activePbMasterInputs
- slave.userData["activePbMasterOutputs"] = activePbMasterOutputs
- printInfo("Active DP slave (addr=%d) I/O pins:" % slave.slaveAddr)
- for sig in activePbMasterOutputs:
- printInfo("DP slave input: " + str(sig))
- for sig in activePbMasterInputs:
- printInfo("DP slave output: " + str(sig))
- self.__configDone = True
- printInfo("HAL configuration done")
- def mainLoop(self, faultDeb):
- master = self.master
- while watchdog() and not self.__configDone:
- self.__tryBuildConfig()
- time.sleep(0.1)
- while watchdog():
- for slave in self.slaves:
- slaveConf = slave.slaveConf
- if slaveConf is not None:
- # Copy I/O data from HAL to PB master output.
- txData = bytearray(slaveConf.inputSize)
- for sig in slave.userData["activePbMasterOutputs"]:
- sig.fromHal(txData)
- slave.setMasterOutData(txData)
- slave = master.run()
- if slave is not None:
- slaveConf = slave.slaveConf
- if slaveConf is not None:
- rxData = slave.getMasterInData()
- halBaseName = "slave.%d" % slave.slaveAddr
- isConnected = slave.isConnected()
- if isConnected: # Slave is connected.
- # Copy I/O data from PB master input to HAL.
- if rxData is not None:
- assert len(rxData) == slaveConf.outputSize
- for sig in slave.userData["activePbMasterInputs"]:
- sig.toHal(rxData)
- self.hal[halBaseName + ".connected"] = True
- self.hal[halBaseName + ".connecting"] = False
- else: # Slave is not connected.
- self.hal[halBaseName + ".connecting"] = slave.isConnecting()
- self.hal[halBaseName + ".connected"] = False
- if self.hal[halBaseName + ".config.disconnect-clear-pins"]:
- # Clear the HAL data.
- rxData = bytearray(slaveConf.outputSize)
- for sig in slave.userData["activePbMasterInputs"]:
- sig.toHal(rxData)
- faultDeb.ok()
- class LinuxCNC_NotRunning(Exception):
- pass
- def printError(msg):
- sys.stderr.write("pyprofibus: " + msg + "\n")
- def printWarning(msg):
- sys.stderr.write("pyprofibus: " + msg + "\n")
- def printInfo(msg):
- sys.stdout.write("pyprofibus: " + msg + "\n")
- # Check presence of LinuxCNC.
- # Returns normally, if LinuxCNC is detected.
- # Raises LinuxCNC_NotRunning, if LinuxCNC is not detected.
- def watchdog():
- # Check whether LinuxCNC is running.
- if fileExists("/tmp/linuxcnc.lock"):
- 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()
- # Create the global LinuxCNC HAL pins and params
- def createGlobalHalPins(hal):
- 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
- hal.newparam("config.ready", HAL_BIT, HAL_RW)
- # Create the per-slave LinuxCNC HAL pins and params
- def createSlaveHalPins(hal, slaveAddr, slaveOutputSize, slaveInputSize):
- 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
- addr = slaveAddr
- hal.newpin("slave.%d.connecting" % addr, HAL_BIT, HAL_OUT)
- hal.newpin("slave.%d.connected" % addr, HAL_BIT, HAL_OUT)
- printInfo("DP slave %d output (MISO): %d bytes" % (addr, slaveOutputSize))
- printInfo("DP slave %d input (MOSI): %d bytes" % (addr, slaveInputSize))
- # Create the input pins
- for i in range(slaveInputSize):
- for bit in range(8):
- hal.newpin("slave.%d.mosi.bit.%d.%d" % (addr, i, bit),
- HAL_BIT, HAL_IN)
- hal.newparam("slave.%d.mosi.bit.%d.%d.active" % (addr, i, bit),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.mosi.u8.%d" % (addr, i),
- HAL_U32, HAL_IN)
- hal.newparam("slave.%d.mosi.u8.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- if i % 2:
- continue
- if slaveInputSize - i < 2:
- continue
- hal.newpin("slave.%d.mosi.u16.%d" % (addr, i),
- HAL_U32, HAL_IN)
- hal.newparam("slave.%d.mosi.u16.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.mosi.s16.%d" % (addr, i),
- HAL_S32, HAL_IN)
- hal.newparam("slave.%d.mosi.s16.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- if slaveInputSize - i < 4:
- continue
- hal.newpin("slave.%d.mosi.u31.%d" % (addr, i),
- HAL_U32, HAL_IN)
- hal.newparam("slave.%d.mosi.u31.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.mosi.s32.%d" % (addr, i),
- HAL_S32, HAL_IN)
- hal.newparam("slave.%d.mosi.s32.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.mosi.float.%d" % (addr, i),
- HAL_FLOAT, HAL_IN)
- hal.newparam("slave.%d.mosi.float.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- # Create the output pins
- for i in range(slaveOutputSize):
- for bit in range(8):
- hal.newpin("slave.%d.miso.bit.%d.%d" % (addr, i, bit),
- HAL_BIT, HAL_OUT)
- hal.newparam("slave.%d.miso.bit.%d.%d.active" % (addr, i, bit),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.miso.u8.%d" % (addr, i),
- HAL_U32, HAL_OUT)
- hal.newparam("slave.%d.miso.u8.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- if i % 2:
- continue
- if slaveOutputSize < 2:
- continue
- hal.newpin("slave.%d.miso.u16.%d" % (addr, i),
- HAL_U32, HAL_OUT)
- hal.newparam("slave.%d.miso.u16.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.miso.s16.%d" % (addr, i),
- HAL_S32, HAL_OUT)
- hal.newparam("slave.%d.miso.s16.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- if slaveOutputSize < 4:
- continue
- hal.newpin("slave.%d.miso.u31.%d" % (addr, i),
- HAL_U32, HAL_OUT)
- hal.newparam("slave.%d.miso.u31.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.miso.s32.%d" % (addr, i),
- HAL_S32, HAL_OUT)
- hal.newparam("slave.%d.miso.s32.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- hal.newpin("slave.%d.miso.float.%d" % (addr, i),
- HAL_FLOAT, HAL_OUT)
- hal.newparam("slave.%d.miso.float.%d.active" % (addr, i),
- HAL_BIT, HAL_RW)
- hal.newparam("slave.%d.config.disconnect-clear-pins" % addr, HAL_BIT, HAL_RW)
- def usage():
- print("pyprofibus-linuxcnc-hal version %s" % VERSION_STRING)
- print("")
- print("Usage: pyprofibus-linuxcnc-hal [OPTIONS] pyprofibus.conf")
- print("")
- print("Options:")
- 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("")
- print("For an example LinuxCNC HAL configuration see:")
- print(" linuxcnc-demo.hal")
- def main():
- global LinuxCNC_HAL
- global opt_loglevel
- global opt_nice
- global opt_watchdog
- opt_loglevel = 3
- opt_nice = None
- opt_watchdog = True
- try:
- (opts, args) = getopt.getopt(sys.argv[1:],
- "hL:N:W:",
- [ "help",
- "loglevel=",
- "nice=",
- "watchdog=", ])
- 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 ("-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 len(args) != 1:
- usage()
- return 1
- configFile = args[0]
- result = 0
- try:
- # Parse the Profibus config file
- config = PbConf.fromFile(configFile)
- if opt_loglevel >= 4 and config.debug < 1:
- config.debug = 1
- # 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("profibus")
- # Create the HAL pins.
- createGlobalHalPins(hal=hal)
- for slaveConf in config.slaveConfs:
- createSlaveHalPins(hal=hal,
- slaveAddr=slaveConf.addr,
- slaveInputSize=slaveConf.inputSize,
- slaveOutputSize=slaveConf.outputSize)
- # Setup the PROFIBUS stack.
- master = config.makeDPM()
- for slaveConf in config.slaveConfs:
- slaveDesc = slaveConf.makeDpSlaveDesc()
- dp1PrmMask = bytearray((
- DpTelegram_SetPrm_Req.DPV1PRM0_FAILSAFE,
- DpTelegram_SetPrm_Req.DPV1PRM1_REDCFG,
- 0x00))
- dp1PrmSet = bytearray((
- DpTelegram_SetPrm_Req.DPV1PRM0_FAILSAFE,
- DpTelegram_SetPrm_Req.DPV1PRM1_REDCFG,
- 0x00))
- slaveDesc.setUserPrmData(
- slaveConf.gsd.getUserPrmData(dp1PrmMask=dp1PrmMask,
- dp1PrmSet=dp1PrmSet))
- master.addSlave(slaveDesc)
- printInfo("Running PROFIBUS-DP master...")
- master.initialize()
- worker = Worker(hal, master)
- hal.ready()
- printInfo("ready.")
- faultDeb = FaultDebouncer()
- while True:
- try:
- worker.mainLoop(faultDeb)
- except (LinuxCNC_NotRunning, KeyboardInterrupt) as e:
- raise e
- except Exception as e:
- if faultDeb.fault() >= 3:
- # Too many faults. Raise a fatal exception.
- printError("Fatal PROFIBUS fault.")
- raise e
- else:
- # Non-fatal fault.
- printError("PROFIBUS fault:\n%s" % str(e))
- except (LinuxCNC_NotRunning, KeyboardInterrupt) as e:
- result = 1
- except PbConfError as e:
- printError("Profibus configuration error:\n%s" % str(e))
- result = 1
- except ProfibusError as e:
- printError("Fatal PROFIBUS fault:\n%s" % str(e))
- result = 1
- printInfo("LinuxCNC HAL module shutdown.")
- return result
- if __name__ == "__main__":
- sys.exit(main())
|