pyprofibus-linuxcnc-hal 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # PROFIBUS DP - LinuxCNC HAL module
  5. #
  6. # Copyright 2016-2021 Michael Buesch <m@bues.ch>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License along
  19. # with this program; if not, write to the Free Software Foundation, Inc.,
  20. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21. #
  22. from __future__ import division, absolute_import, print_function, unicode_literals
  23. import sys
  24. import os
  25. import time
  26. import getopt
  27. import struct
  28. from pyprofibus import ProfibusError, FdlError, DpError
  29. from pyprofibus.compat import isPy2Compat
  30. from pyprofibus.util import fileExists, monotonic_time
  31. from pyprofibus.dp import DpTelegram_SetPrm_Req
  32. from pyprofibus.conf import PbConf, PbConfError
  33. from pyprofibus.version import *
  34. class SigBit(object):
  35. def __init__(self, hal, halName, byteOffset, bitOffset):
  36. self.hal = hal
  37. self.halName = halName
  38. self.byteOffset = byteOffset
  39. self.bitOffset = bitOffset
  40. self.setMask = 1 << bitOffset
  41. self.clrMask = ~(1 << bitOffset)
  42. def fromHal(self, destBuf):
  43. if self.hal[self.halName]:
  44. destBuf[self.byteOffset] |= self.setMask
  45. else:
  46. destBuf[self.byteOffset] &= self.clrMask
  47. def toHal(self, srcBuf):
  48. self.hal[self.halName] = (srcBuf[self.byteOffset] >> self.bitOffset) & 1
  49. def __str__(self):
  50. return "profibus.%s" % self.halName
  51. class SigU8(object):
  52. def __init__(self, hal, halName, offset):
  53. self.hal = hal
  54. self.halName = halName
  55. self.offset = offset
  56. def fromHal(self, destBuf):
  57. destBuf[self.offset] = self.hal[self.halName] & 0xFF
  58. def toHal(self, srcBuf):
  59. self.hal[self.halName] = srcBuf[self.offset] & 0xFF
  60. def __str__(self):
  61. return "profibus.%s" % self.halName
  62. class SigU16(object):
  63. def __init__(self, hal, halName, offset):
  64. self.hal = hal
  65. self.halName = halName
  66. self.offset = offset
  67. def fromHal(self, destBuf):
  68. word = self.hal[self.halName] & 0xFFFF
  69. destBuf[self.offset] = (word >> 8) & 0xFF
  70. destBuf[self.offset + 1] = word & 0xFF
  71. def toHal(self, srcBuf):
  72. word = (srcBuf[self.offset] << 8) |\
  73. srcBuf[self.offset + 1]
  74. self.hal[self.halName] = word & 0xFFFF
  75. def __str__(self):
  76. return "profibus.%s" % self.halName
  77. class SigS16(object):
  78. def __init__(self, hal, halName, offset):
  79. self.hal = hal
  80. self.halName = halName
  81. self.offset = offset
  82. def fromHal(self, destBuf):
  83. word = self.hal[self.halName] & 0xFFFF
  84. destBuf[self.offset] = (word >> 8) & 0xFF
  85. destBuf[self.offset + 1] = word & 0xFF
  86. def toHal(self, srcBuf):
  87. word = (srcBuf[self.offset] << 8) |\
  88. srcBuf[self.offset + 1]
  89. if word & 0x8000:
  90. self.hal[self.halName] = -((~word + 1) & 0xFFFF)
  91. else:
  92. self.hal[self.halName] = word & 0xFFFF
  93. def __str__(self):
  94. return "profibus.%s" % self.halName
  95. class SigU31(object):
  96. def __init__(self, hal, halName, offset):
  97. self.hal = hal
  98. self.halName = halName
  99. self.offset = offset
  100. def fromHal(self, destBuf):
  101. dword = self.hal[self.halName] & 0x7FFFFFFF
  102. destBuf[self.offset] = (dword >> 24) & 0xFF
  103. destBuf[self.offset + 1] = (dword >> 16) & 0xFF
  104. destBuf[self.offset + 2] = (dword >> 8) & 0xFF
  105. destBuf[self.offset + 3] = dword & 0xFF
  106. def toHal(self, srcBuf):
  107. dword = (srcBuf[self.offset] << 24) |\
  108. (srcBuf[self.offset + 1] << 16) |\
  109. (srcBuf[self.offset + 2] << 8) |\
  110. srcBuf[self.offset + 3]
  111. self.hal[self.halName] = dword & 0x7FFFFFFF
  112. def __str__(self):
  113. return "profibus.%s" % self.halName
  114. class SigS32(object):
  115. def __init__(self, hal, halName, offset):
  116. self.hal = hal
  117. self.halName = halName
  118. self.offset = offset
  119. def fromHal(self, destBuf):
  120. dword = self.hal[self.halName] & 0xFFFFFFFF
  121. destBuf[self.offset] = (dword >> 24) & 0xFF
  122. destBuf[self.offset + 1] = (dword >> 16) & 0xFF
  123. destBuf[self.offset + 2] = (dword >> 8) & 0xFF
  124. destBuf[self.offset + 3] = dword & 0xFF
  125. def toHal(self, srcBuf):
  126. dword = (srcBuf[self.offset] << 24) |\
  127. (srcBuf[self.offset + 1] << 16) |\
  128. (srcBuf[self.offset + 2] << 8) |\
  129. srcBuf[self.offset + 3]
  130. if dword & 0x80000000:
  131. self.hal[self.halName] = -((~dword + 1) & 0xFFFFFFFF)
  132. else:
  133. self.hal[self.halName] = dword & 0xFFFFFFFF
  134. def __str__(self):
  135. return "profibus.%s" % self.halName
  136. class SigFloat(object):
  137. floatStruct = struct.Struct(str('>f'))
  138. def __init__(self, hal, halName, offset):
  139. self.hal = hal
  140. self.halName = halName
  141. self.offset = offset
  142. def fromHal(self, destBuf):
  143. buf = self.floatStruct.pack(self.hal[self.halName])
  144. if isPy2Compat:
  145. buf = [ ord(b) for b in buf ]
  146. destBuf[self.offset : self.offset + 4] = buf[0 : 4]
  147. def toHal(self, srcBuf):
  148. dword = (srcBuf[self.offset] << 24) |\
  149. (srcBuf[self.offset + 1] << 16) |\
  150. (srcBuf[self.offset + 2] << 8) |\
  151. srcBuf[self.offset + 3]
  152. if isPy2Compat:
  153. value = self.floatStruct.unpack(
  154. chr((dword >> 24) & 0xFF) +\
  155. chr((dword >> 16) & 0xFF) +\
  156. chr((dword >> 8) & 0xFF) +\
  157. chr(dword & 0xFF)
  158. )[0]
  159. else:
  160. value = self.floatStruct.unpack(
  161. bytes( ((dword >> 24) & 0xFF,
  162. (dword >> 16) & 0xFF,
  163. (dword >> 8) & 0xFF,
  164. dword & 0xFF)
  165. )
  166. )[0]
  167. self.hal[self.halName] = value
  168. def __str__(self):
  169. return "profibus.%s" % self.halName
  170. class Worker(object):
  171. def __init__(self, hal, master):
  172. self.__configDone = False
  173. self.hal = hal
  174. self.master = master
  175. self.slaves = master.getSlaveList()
  176. def __buildTable(self, slaveAddr, direction, size):
  177. tab = []
  178. for i in range(0, size):
  179. for bitNr in range(8):
  180. halName = "slave.%d.%s.bit.%d.%d" % (
  181. slaveAddr, direction, i, bitNr)
  182. if self.hal[halName + ".active"]:
  183. tab.append(SigBit(self.hal, halName,
  184. i, bitNr))
  185. halName = "slave.%d.%s.u8.%d" % (
  186. slaveAddr, direction, i)
  187. if self.hal[halName + ".active"]:
  188. tab.append(SigU8(self.hal, halName, i))
  189. if i % 2:
  190. continue
  191. if size - i < 2:
  192. continue
  193. halName = "slave.%d.%s.u16.%d" % (
  194. slaveAddr, direction, i)
  195. if self.hal[halName + ".active"]:
  196. tab.append(SigU16(self.hal, halName, i))
  197. halName = "slave.%d.%s.s16.%d" % (
  198. slaveAddr, direction, i)
  199. if self.hal[halName + ".active"]:
  200. tab.append(SigS16(self.hal, halName, i))
  201. if size - i < 4:
  202. continue
  203. halName = "slave.%d.%s.u31.%d" % (
  204. slaveAddr, direction, i)
  205. if self.hal[halName + ".active"]:
  206. tab.append(SigU31(self.hal, halName, i))
  207. halName = "slave.%d.%s.s32.%d" % (
  208. slaveAddr, direction, i)
  209. if self.hal[halName + ".active"]:
  210. tab.append(SigS32(self.hal, halName, i))
  211. halName = "slave.%d.%s.float.%d" % (
  212. slaveAddr, direction, i)
  213. if self.hal[halName + ".active"]:
  214. tab.append(SigFloat(self.hal, halName, i))
  215. return tab
  216. def __tryBuildConfig(self):
  217. if not self.hal["config.ready"]:
  218. return
  219. for slave in self.slaves:
  220. slaveConf = slave.slaveConf
  221. if slaveConf is None:
  222. continue
  223. activePbMasterOutputs = self.__buildTable(
  224. slave.slaveAddr, "input", slaveConf.inputSize)
  225. activePbMasterInputs = self.__buildTable(
  226. slave.slaveAddr, "output", slaveConf.outputSize)
  227. slave.userData["activePbMasterInputs"] = activePbMasterInputs
  228. slave.userData["activePbMasterOutputs"] = activePbMasterOutputs
  229. printInfo("Active DP slave (addr=%d) pins:" % slave.slaveAddr)
  230. for sig in activePbMasterOutputs:
  231. printInfo("DP slave input: " + str(sig))
  232. for sig in activePbMasterInputs:
  233. printInfo("DP slave output: " + str(sig))
  234. self.__configDone = True
  235. printInfo("HAL configuration done")
  236. def mainLoop(self):
  237. master = self.master
  238. while watchdog() and not self.__configDone:
  239. self.__tryBuildConfig()
  240. time.sleep(0.1)
  241. while watchdog():
  242. for slave in self.slaves:
  243. slaveConf = slave.slaveConf
  244. if slaveConf is not None:
  245. txData = bytearray(slaveConf.inputSize)
  246. for sig in slave.userData["activePbMasterOutputs"]:
  247. sig.fromHal(txData)
  248. slave.setMasterOutData(txData)
  249. slave = master.run()
  250. if slave:
  251. slaveConf = slave.slaveConf
  252. if slaveConf is not None:
  253. rxData = slave.getMasterInData()
  254. if rxData:
  255. slaveOutputSize = slaveConf.outputSize
  256. if len(rxData) > slaveOutputSize:
  257. rxData = rxData[0 : slaveOutputSize]
  258. if len(rxData) < slaveOutputSize:
  259. rxData += b'\0' * (slaveOutputSize - len(rxData))
  260. for sig in slave.userData["activePbMasterInputs"]:
  261. sig.toHal(rxData)
  262. class LinuxCNC_NotRunning(Exception):
  263. pass
  264. def printError(msg):
  265. sys.stderr.write("pyprofibus: " + msg + "\n")
  266. def printWarning(msg):
  267. sys.stderr.write("pyprofibus: " + msg + "\n")
  268. def printInfo(msg):
  269. sys.stdout.write("pyprofibus: " + msg + "\n")
  270. # Check presence of LinuxCNC.
  271. # Returns normally, if LinuxCNC is detected.
  272. # Raises LinuxCNC_NotRunning, if LinuxCNC is not detected.
  273. def watchdog():
  274. # Check whether LinuxCNC is running.
  275. for lockname in ("/tmp/linuxcnc.lock", "/tmp/emc.lock"):
  276. if fileExists(lockname):
  277. return True
  278. if not opt_watchdog:
  279. # The check is disabled. Return success.
  280. return True
  281. printError("LinuxCNC doesn't seem to be running. "\
  282. "(Use '--watchdog off' to disable this check.)")
  283. raise LinuxCNC_NotRunning()
  284. # Create the LinuxCNC HAL pins
  285. def createHalPins(hal, slaveAddr, slaveOutputSize, slaveInputSize):
  286. HAL_BIT, HAL_U32, HAL_S32, HAL_FLOAT = \
  287. LinuxCNC_HAL.HAL_BIT, LinuxCNC_HAL.HAL_U32, \
  288. LinuxCNC_HAL.HAL_S32, LinuxCNC_HAL.HAL_FLOAT
  289. HAL_IN, HAL_OUT, HAL_RO, HAL_RW = \
  290. LinuxCNC_HAL.HAL_IN, LinuxCNC_HAL.HAL_OUT, \
  291. LinuxCNC_HAL.HAL_RO, LinuxCNC_HAL.HAL_RW
  292. addr = slaveAddr
  293. printInfo("DP slave %d output: %d bytes" % (addr, slaveOutputSize))
  294. printInfo("DP slave %d input: %d bytes" % (addr, slaveInputSize))
  295. # Create the input pins
  296. for i in range(slaveInputSize):
  297. for bit in range(8):
  298. hal.newpin("slave.%d.input.bit.%d.%d" % (addr, i, bit),
  299. HAL_BIT, HAL_IN)
  300. hal.newparam("slave.%d.input.bit.%d.%d.active" % (addr, i, bit),
  301. HAL_BIT, HAL_RW)
  302. hal.newpin("slave.%d.input.u8.%d" % (addr, i),
  303. HAL_U32, HAL_IN)
  304. hal.newparam("slave.%d.input.u8.%d.active" % (addr, i),
  305. HAL_BIT, HAL_RW)
  306. if i % 2:
  307. continue
  308. if slaveInputSize - i < 2:
  309. continue
  310. hal.newpin("slave.%d.input.u16.%d" % (addr, i),
  311. HAL_U32, HAL_IN)
  312. hal.newparam("slave.%d.input.u16.%d.active" % (addr, i),
  313. HAL_BIT, HAL_RW)
  314. hal.newpin("slave.%d.input.s16.%d" % (addr, i),
  315. HAL_S32, HAL_IN)
  316. hal.newparam("slave.%d.input.s16.%d.active" % (addr, i),
  317. HAL_BIT, HAL_RW)
  318. if slaveInputSize - i < 4:
  319. continue
  320. hal.newpin("slave.%d.input.u31.%d" % (addr, i),
  321. HAL_U32, HAL_IN)
  322. hal.newparam("slave.%d.input.u31.%d.active" % (addr, i),
  323. HAL_BIT, HAL_RW)
  324. hal.newpin("slave.%d.input.s32.%d" % (addr, i),
  325. HAL_S32, HAL_IN)
  326. hal.newparam("slave.%d.input.s32.%d.active" % (addr, i),
  327. HAL_BIT, HAL_RW)
  328. hal.newpin("slave.%d.input.float.%d" % (addr, i),
  329. HAL_FLOAT, HAL_IN)
  330. hal.newparam("slave.%d.input.float.%d.active" % (addr, i),
  331. HAL_BIT, HAL_RW)
  332. # Create the output pins
  333. for i in range(slaveOutputSize):
  334. for bit in range(8):
  335. hal.newpin("slave.%d.output.bit.%d.%d" % (addr, i, bit),
  336. HAL_BIT, HAL_OUT)
  337. hal.newparam("slave.%d.output.bit.%d.%d.active" % (addr, i, bit),
  338. HAL_BIT, HAL_RW)
  339. hal.newpin("slave.%d.output.u8.%d" % (addr, i),
  340. HAL_U32, HAL_OUT)
  341. hal.newparam("slave.%d.output.u8.%d.active" % (addr, i),
  342. HAL_BIT, HAL_RW)
  343. if i % 2:
  344. continue
  345. if slaveOutputSize < 2:
  346. continue
  347. hal.newpin("slave.%d.output.u16.%d" % (addr, i),
  348. HAL_U32, HAL_OUT)
  349. hal.newparam("slave.%d.output.u16.%d.active" % (addr, i),
  350. HAL_BIT, HAL_RW)
  351. hal.newpin("slave.%d.output.s16.%d" % (addr, i),
  352. HAL_S32, HAL_OUT)
  353. hal.newparam("slave.%d.output.s16.%d.active" % (addr, i),
  354. HAL_BIT, HAL_RW)
  355. if slaveOutputSize < 4:
  356. continue
  357. hal.newpin("slave.%d.output.u31.%d" % (addr, i),
  358. HAL_U32, HAL_OUT)
  359. hal.newparam("slave.%d.output.u31.%d.active" % (addr, i),
  360. HAL_BIT, HAL_RW)
  361. hal.newpin("slave.%d.output.s32.%d" % (addr, i),
  362. HAL_S32, HAL_OUT)
  363. hal.newparam("slave.%d.output.s32.%d.active" % (addr, i),
  364. HAL_BIT, HAL_RW)
  365. hal.newpin("slave.%d.output.float.%d" % (addr, i),
  366. HAL_FLOAT, HAL_OUT)
  367. hal.newparam("slave.%d.output.float.%d.active" % (addr, i),
  368. HAL_BIT, HAL_RW)
  369. hal.newparam("config.ready", HAL_BIT, HAL_RW)
  370. def usage():
  371. print("pyprofibus-linuxcnc-hal version %s" % VERSION_STRING)
  372. print("")
  373. print("Usage: pyprofibus-linuxcnc-hal [OPTIONS] pyprofibus.conf")
  374. print("")
  375. print("Options:")
  376. print("")
  377. print(" -L|--loglevel LVL Set the log level:")
  378. print(" 0: Log nothing")
  379. print(" 1: Log errors")
  380. print(" 2: Log errors and warnings")
  381. print(" 3: Log errors, warnings and info messages (default)")
  382. print(" 4: Verbose logging")
  383. print(" 5: Extremely verbose logging")
  384. print(" -N|--nice NICE Renice the process. -20 <= NICE <= 19.")
  385. print(" Default: Do not renice")
  386. print("")
  387. print("Debugging options:")
  388. print(" -W|--watchdog 1/0 Enable/disable LinuxCNC runtime watchdog.")
  389. print(" Default: on")
  390. print("")
  391. print("For an example LinuxCNC HAL configuration see:")
  392. print(" linuxcnc-demo.hal")
  393. def main():
  394. global LinuxCNC_HAL
  395. global opt_loglevel
  396. global opt_nice
  397. global opt_watchdog
  398. opt_loglevel = 3
  399. opt_nice = None
  400. opt_watchdog = True
  401. try:
  402. (opts, args) = getopt.getopt(sys.argv[1:],
  403. "hL:N:W:",
  404. [ "help",
  405. "loglevel=",
  406. "nice=",
  407. "watchdog=", ])
  408. except getopt.GetoptError as e:
  409. printError(str(e))
  410. usage()
  411. return 1
  412. for (o, v) in opts:
  413. if o in ("-h", "--help"):
  414. usage()
  415. return 0
  416. if o in ("-L", "--loglevel"):
  417. try:
  418. opt_loglevel = int(v)
  419. except ValueError:
  420. printError("-L|--loglevel: Invalid log level")
  421. return 1
  422. if o in ("-N", "--nice"):
  423. try:
  424. opt_nice = int(v)
  425. if opt_nice < -20 or opt_nice > 19:
  426. raise ValueError
  427. except ValueError:
  428. printError("-N|--nice: Invalid niceness level")
  429. return 1
  430. if o in ("-W", "--watchdog"):
  431. opt_watchdog = str2bool(v)
  432. if len(args) != 1:
  433. usage()
  434. return 1
  435. configFile = args[0]
  436. result = 0
  437. try:
  438. # Parse the Profibus config file
  439. config = PbConf.fromFile(configFile)
  440. if opt_loglevel >= 4 and config.debug < 1:
  441. config.debug = 1
  442. # Adjust process priority
  443. if opt_nice is not None:
  444. try:
  445. os.nice(opt_nice)
  446. except OSError as e:
  447. printError("Failed to renice process to "
  448. "%d: %s" % (opt_nice, str(e)))
  449. return 1
  450. # Try to import the LinuxCNC HAL module
  451. try:
  452. import hal as LinuxCNC_HAL
  453. except ImportError as e:
  454. printError("Failed to import LinuxCNC HAL "
  455. "module: %s" % str(e))
  456. return 1
  457. # Create the LinuxCNC HAL component.
  458. hal = LinuxCNC_HAL.component("profibus")
  459. # Create the HAL pins.
  460. for slaveConf in config.slaveConfs:
  461. createHalPins(hal=hal,
  462. slaveAddr=slaveConf.addr,
  463. slaveInputSize=slaveConf.inputSize,
  464. slaveOutputSize=slaveConf.outputSize)
  465. # Setup the PROFIBUS stack.
  466. master = config.makeDPM()
  467. for slaveConf in config.slaveConfs:
  468. slaveDesc = slaveConf.makeDpSlaveDesc()
  469. dp1PrmMask = bytearray((
  470. DpTelegram_SetPrm_Req.DPV1PRM0_FAILSAFE,
  471. DpTelegram_SetPrm_Req.DPV1PRM1_REDCFG,
  472. 0x00))
  473. dp1PrmSet = bytearray((
  474. DpTelegram_SetPrm_Req.DPV1PRM0_FAILSAFE,
  475. DpTelegram_SetPrm_Req.DPV1PRM1_REDCFG,
  476. 0x00))
  477. slaveDesc.setUserPrmData(
  478. slaveConf.gsd.getUserPrmData(dp1PrmMask=dp1PrmMask,
  479. dp1PrmSet=dp1PrmSet))
  480. master.addSlave(slaveDesc)
  481. printInfo("Running PROFIBUS-DP master...")
  482. master.initialize()
  483. worker = Worker(hal, master)
  484. hal.ready()
  485. printInfo("ready.")
  486. lastExceptionTime = None
  487. while True:
  488. try:
  489. worker.mainLoop()
  490. except ProfibusError as e:
  491. now = monotonic_time()
  492. if lastExceptionTime is not None and\
  493. now - lastExceptionTime < 1.0:
  494. # The last fault is less than one second
  495. # in the past. Raise a fatal exception.
  496. printError("Fatal fault detected")
  497. raise e
  498. else:
  499. lastExceptionTime = now
  500. # Non-fatal fault.
  501. printError("PROFIBUS fault:\n%s" % str(e))
  502. except LinuxCNC_NotRunning as e:
  503. result = 1
  504. except KeyboardInterrupt as e:
  505. result = 1
  506. except PbConfError as e:
  507. printError("Profibus configuration error:\n%s" % str(e))
  508. result = 1
  509. except ProfibusError as e:
  510. printError("Fatal PROFIBUS fault:\n%s" % str(e))
  511. result = 1
  512. printInfo("LinuxCNC HAL module shutdown.")
  513. return result
  514. if __name__ == "__main__":
  515. sys.exit(main())