conf.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. # -*- coding: utf-8 -*-
  2. #
  3. # PROFIBUS DP - Configuration file parser
  4. #
  5. # Copyright (c) 2016-2021 Michael Buesch <m@bues.ch>
  6. #
  7. # Licensed under the terms of the GNU General Public License version 2,
  8. # or (at your option) any later version.
  9. #
  10. from __future__ import division, absolute_import, print_function, unicode_literals
  11. from pyprofibus.compat import *
  12. from pyprofibus.gsd.interp import GsdInterp
  13. from pyprofibus.gsd.parser import GsdError
  14. from pyprofibus.util import *
  15. import re
  16. import sys
  17. from io import StringIO
  18. if isPy2Compat:
  19. from ConfigParser import SafeConfigParser as _ConfigParser
  20. from ConfigParser import Error as _ConfigParserError
  21. else:
  22. from configparser import ConfigParser as _ConfigParser
  23. from configparser import Error as _ConfigParserError
  24. __all__ = [
  25. "PbConfError",
  26. "PbConf",
  27. ]
  28. def loadGsd(name, debug=0):
  29. exceptions = []
  30. # Try name as a direct file name.
  31. try:
  32. return GsdInterp.fromFile(name, debug=(debug > 0))
  33. except GsdError as e:
  34. exceptions.append(str(e))
  35. # Try to interpret the name as a module name.
  36. try:
  37. if "/" not in name:
  38. mod = name.replace(".gsd", "_gsd")
  39. return GsdInterp.fromPy(mod, debug=(debug > 0))
  40. except GsdError as e:
  41. exceptions.append(str(e))
  42. # Try to interpret the name as a module name with leading fs path components stripped.
  43. try:
  44. slashIdx = name.rfind("/")
  45. if slashIdx >= 0:
  46. mod = name[slashIdx + 1 : ].replace(".gsd", "_gsd")
  47. return GsdInterp.fromPy(mod, debug=(debug > 0))
  48. except GsdError as e:
  49. exceptions.append(str(e))
  50. raise GsdError("\n".join(exceptions))
  51. class PbConfError(ProfibusError):
  52. pass
  53. class PbConf(object):
  54. """Pyprofibus configuration file parser.
  55. """
  56. class _SlaveConf(object):
  57. """Slave configuration.
  58. """
  59. index = None
  60. name = None
  61. addr = None
  62. gsd = None
  63. syncMode = None
  64. freezeMode = None
  65. groupMask = None
  66. watchdogMs = None
  67. inputSize = None
  68. outputSize = None
  69. diagPeriod = None
  70. def makeDpSlaveDesc(self):
  71. """Create a DpSlaveDesc instance based on the configuration.
  72. """
  73. from pyprofibus.dp_master import DpSlaveDesc
  74. slaveDesc = DpSlaveDesc(self)
  75. # Create Chk_Cfg telegram
  76. slaveDesc.setCfgDataElements(self.gsd.getCfgDataElements())
  77. # Set User_Prm_Data
  78. slaveDesc.setUserPrmData(self.gsd.getUserPrmData())
  79. # Set various standard parameters
  80. slaveDesc.setSyncMode(self.syncMode)
  81. slaveDesc.setFreezeMode(self.freezeMode)
  82. slaveDesc.setGroupMask(self.groupMask)
  83. slaveDesc.setWatchdog(self.watchdogMs)
  84. return slaveDesc
  85. # [PROFIBUS] section
  86. debug = None
  87. # [PHY] section
  88. phyType = None
  89. phyDev = None
  90. phyBaud = None
  91. phyRtsCts = None
  92. phyDsrDtr = None
  93. phySpiBus = None
  94. phySpiCS = None
  95. phySpiSpeedHz = None
  96. # [DP] section
  97. dpMasterClass = None
  98. dpMasterAddr = None
  99. # [SLAVE_xxx] sections
  100. slaveConfs = None
  101. @classmethod
  102. def fromFile(cls, filename):
  103. if isPy2Compat:
  104. with open(filename, "r") as fd:
  105. return cls(fd, filename)
  106. else:
  107. with open(filename, "r", encoding="UTF-8") as fd:
  108. return cls(fd, filename)
  109. __reSlave = re.compile(r'^SLAVE_(\d+)$')
  110. __reMod = re.compile(r'^module_(\d+)$')
  111. def __init__(self, fd, filename=None):
  112. def get(section, option, fallback = None):
  113. if p.has_option(section, option):
  114. return p.get(section, option)
  115. if fallback is None:
  116. raise ValueError("Option [%s] '%s' does not exist." % (
  117. section, option))
  118. return fallback
  119. def getboolean(section, option, fallback = None):
  120. if p.has_option(section, option):
  121. return p.getboolean(section, option)
  122. if fallback is None:
  123. raise ValueError("Option [%s] '%s' does not exist." % (
  124. section, option))
  125. return fallback
  126. def getint(section, option, fallback = None):
  127. if p.has_option(section, option):
  128. return p.getint(section, option)
  129. if fallback is None:
  130. raise ValueError("Option [%s] '%s' does not exist." % (
  131. section, option))
  132. return fallback
  133. try:
  134. p = _ConfigParser()
  135. if hasattr(p, "read_file"):
  136. p.read_file(fd, filename)
  137. else:
  138. p.readfp(fd, filename)
  139. # [PROFIBUS]
  140. self.debug = getint("PROFIBUS", "debug",
  141. fallback=0)
  142. # [PHY]
  143. self.phyType = get("PHY", "type",
  144. fallback="serial")
  145. self.phyDev = get("PHY", "dev",
  146. fallback="/dev/ttyS0")
  147. self.phyBaud = getint("PHY", "baud",
  148. fallback=9600)
  149. self.phyRtsCts = getboolean("PHY", "rtscts",
  150. fallback=False)
  151. self.phyDsrDtr = getboolean("PHY", "dsrdtr",
  152. fallback=False)
  153. self.phySpiBus = getint("PHY", "spiBus",
  154. fallback=0)
  155. self.phySpiCS = getint("PHY", "spiCS",
  156. fallback=0)
  157. self.phySpiSpeedHz = getint("PHY", "spiSpeedHz",
  158. fallback=1000000)
  159. # [DP]
  160. self.dpMasterClass = getint("DP", "master_class",
  161. fallback=1)
  162. if self.dpMasterClass not in {1, 2}:
  163. raise ValueError("Invalid master_class")
  164. self.dpMasterAddr = getint("DP", "master_addr",
  165. fallback=0x02)
  166. if self.dpMasterAddr < 0 or self.dpMasterAddr > 127:
  167. raise ValueError("Invalid master_addr")
  168. self.slaveConfs = []
  169. for section in p.sections():
  170. m = self.__reSlave.match(section)
  171. if not m:
  172. continue
  173. index = int(m.group(1))
  174. s = self._SlaveConf()
  175. s.index = index
  176. s.name = get(section, "name", section)
  177. s.addr = getint(section, "addr")
  178. s.gsd = loadGsd(get(section, "gsd"), self.debug)
  179. s.syncMode = getboolean(section, "sync_mode",
  180. fallback=False)
  181. s.freezeMode = getboolean(section, "freeze_mode",
  182. fallback=False)
  183. s.groupMask = getboolean(section, "group_mask",
  184. fallback=1)
  185. if s.groupMask < 0 or s.groupMask > 0xFF:
  186. raise ValueError("Invalid group_mask")
  187. s.watchdogMs = getint(section, "watchdog_ms",
  188. fallback=5000)
  189. if s.watchdogMs < 0 or s.watchdogMs > 255 * 255:
  190. raise ValueError("Invalid watchdog_ms")
  191. s.inputSize = getint(section, "input_size")
  192. if s.inputSize < 0 or s.inputSize > 246:
  193. raise ValueError("Invalid input_size")
  194. s.outputSize = getint(section, "output_size")
  195. if s.outputSize < 0 or s.outputSize > 246:
  196. raise ValueError("Invalid output_size")
  197. s.diagPeriod = getint(section, "diag_period", 0)
  198. if s.diagPeriod < 0 or s.diagPeriod > 0x3FFFFFFF:
  199. raise ValueError("Invalid diag_period")
  200. mods = [ o for o in p.options(section)
  201. if self.__reMod.match(o) ]
  202. mods.sort(key = lambda o: int(self.__reMod.match(o).group(1)))
  203. if s.gsd.isModular():
  204. for option in mods:
  205. s.gsd.setConfiguredModule(get(section, option))
  206. elif mods:
  207. print("Warning: Some modules are specified in the config file, "
  208. "but the station is 'Compact': Modular_Station=0.",
  209. file=sys.stderr)
  210. self.slaveConfs.append(s)
  211. except (IOError, UnicodeError) as e:
  212. raise PbConfError("Failed to read '%s': %s" %\
  213. (filename, str(e)))
  214. except (_ConfigParserError, ValueError) as e:
  215. raise PbConfError("Profibus config file parse "
  216. "error:\n%s" % str(e))
  217. except GsdError as e:
  218. raise PbConfError("Failed to parse GSD file:\n%s" % str(e))
  219. def makePhy(self):
  220. """Create a CP-PHY instance based on the configuration.
  221. """
  222. phyType = self.phyType.lower().strip()
  223. if phyType == "serial":
  224. import pyprofibus.phy_serial
  225. phyClass = pyprofibus.phy_serial.CpPhySerial
  226. extraKwArgs = {}
  227. elif phyType in {"dummyslave", "dummy_slave", "dummy-slave"}:
  228. import pyprofibus.phy_dummy
  229. phyClass = pyprofibus.phy_dummy.CpPhyDummySlave
  230. extraKwArgs = {
  231. "echoDX" : all(slaveConf.outputSize > 0
  232. for slaveConf in self.slaveConfs),
  233. "echoDXSize" : max(slaveConf.outputSize
  234. for slaveConf in self.slaveConfs),
  235. }
  236. elif phyType == "fpga":
  237. import pyprofibus.phy_fpga
  238. phyClass = pyprofibus.phy_fpga.CpPhyFPGA
  239. extraKwArgs = {}
  240. else:
  241. raise PbConfError("Invalid phyType parameter value: "
  242. "%s" % self.phyType)
  243. phy = phyClass(debug=(self.debug >= 2),
  244. port=self.phyDev,
  245. spiBus=self.phySpiBus,
  246. spiCS=self.phySpiCS,
  247. spiSpeedHz=self.phySpiSpeedHz,
  248. **extraKwArgs)
  249. phy.setConfig(baudrate=self.phyBaud,
  250. rtscts=self.phyRtsCts,
  251. dsrdtr=self.phyDsrDtr)
  252. return phy
  253. def makeDPM(self, phy=None):
  254. """Create a DpMaster and a CP-PHY instance based on the configuration.
  255. Returns the DpMaster instance.
  256. """
  257. if phy is None:
  258. # Create a PHY (layer 1) interface object.
  259. phy = self.makePhy()
  260. # Create a DP class 1 or 2 master.
  261. from pyprofibus.dp_master import DPM1, DPM2
  262. if self.dpMasterClass == 1:
  263. DpMasterClass = DPM1
  264. elif self.dpMasterClass == 2:
  265. DpMasterClass = DPM2
  266. else:
  267. raise PbConfError("Invalid dpMasterClass parameter value: "
  268. "%d" % self.dpMasterClass)
  269. master = DpMasterClass(phy=phy,
  270. masterAddr=self.dpMasterAddr,
  271. debug=(self.debug >= 1))
  272. return master