dp_master.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. # -*- coding: utf-8 -*-
  2. #
  3. # PROFIBUS DP - Master
  4. #
  5. # Copyright (c) 2013-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.fdl import *
  13. from pyprofibus.dp import *
  14. from pyprofibus.util import *
  15. import gc
  16. import math
  17. __all__ = [
  18. "DpSlaveDesc",
  19. "DPM1",
  20. "DPM2",
  21. ]
  22. class DpSlaveState(object):
  23. """Run time state of a DP slave that is managed by a DPM instance.
  24. """
  25. _STATE_INVALID = -1
  26. STATE_INIT = 0 # Initialize
  27. STATE_WDIAG = 1 # Wait for diagnosis
  28. STATE_WPRM = 2 # Wait for Prm telegram
  29. STATE_WCFG = 3 # Wait for Cfg telegram
  30. STATE_WDXRDY = 4 # Wait for Data_Exchange ready
  31. STATE_DX = 5 # Data_Exchange
  32. state2name = {
  33. _STATE_INVALID : "Invalid",
  34. STATE_INIT : "Init",
  35. STATE_WDIAG : "Wait for diag",
  36. STATE_WPRM : "Wait for Prm",
  37. STATE_WCFG : "Wait for Cfg",
  38. STATE_WDXRDY : "Request diag and wait for DX-ready",
  39. STATE_DX : "Data_Exchange",
  40. }
  41. # State timeouts in seconds
  42. stateTimeLimits = {
  43. STATE_INIT : TimeLimit.UNLIMITED,
  44. STATE_WDIAG : 1.0,
  45. STATE_WPRM : 0.5,
  46. STATE_WCFG : 0.5,
  47. STATE_WDXRDY : 1.0,
  48. STATE_DX : 0.5,
  49. }
  50. __slots__ = (
  51. "__nextState",
  52. "__prevState",
  53. "__state",
  54. "__stateTimeout",
  55. "dxStartTime",
  56. "dxCount",
  57. "dxCycleRunning",
  58. "faultDeb",
  59. "fcb",
  60. "master",
  61. "fromSlaveData",
  62. "toSlaveData",
  63. "pendingReq",
  64. "pendingReqTimeout",
  65. "rxQueue",
  66. "shortAckReceived",
  67. "slaveDesc",
  68. )
  69. def __init__(self, master, slaveDesc):
  70. self.master = master
  71. self.slaveDesc = slaveDesc
  72. # Fault counter
  73. self.faultDeb = FaultDebouncer()
  74. self.__state = self._STATE_INVALID
  75. self.__nextState = self._STATE_INVALID
  76. self.__prevState = self._STATE_INVALID
  77. self.__stateTimeout = TimeLimit()
  78. # Context for FC-Bit toggeling
  79. self.fcb = FdlFCB()
  80. self.setState(self.STATE_INIT)
  81. self.applyState()
  82. # Currently running request telegram
  83. self.pendingReq = None
  84. self.pendingReqTimeout = TimeLimit()
  85. self.shortAckReceived = False
  86. # Data_Exchange context
  87. self.dxStartTime = 0.0
  88. self.dxCount = 0
  89. # Received telegrams
  90. self.rxQueue = []
  91. # In/Out user data
  92. self.toSlaveData = None
  93. self.fromSlaveData = None
  94. def getRxQueue(self):
  95. rxQueue = self.rxQueue
  96. self.rxQueue = []
  97. return rxQueue
  98. def flushRxQueue(self):
  99. self.rxQueue = []
  100. def getState(self):
  101. return self.__state
  102. def getNextState(self):
  103. return self.__nextState
  104. def setState(self, state, stateTimeLimit=None):
  105. if stateTimeLimit is None:
  106. stateTimeLimit = self.stateTimeLimits[state]
  107. if state == self.STATE_INIT:
  108. self.dxCycleRunning = False
  109. self.fcb.resetFCB()
  110. self.__nextState = state
  111. self.__stateTimeout.start(stateTimeLimit)
  112. self.master.phy.clearTxQueueAddr(self.slaveDesc.slaveAddr)
  113. self.master._releaseSlave(self)
  114. def applyState(self):
  115. # Enter the new state
  116. self.__prevState, self.__state = self.__state, self.__nextState
  117. # Handle state switch
  118. if self.stateJustEntered():
  119. self.pendingReq = None
  120. def stateJustEntered(self):
  121. # Returns True, if the state was just entered.
  122. return self.__prevState != self.__state
  123. def stateIsChanging(self):
  124. # Returns True, if the state was just changed.
  125. return self.__nextState != self.__state
  126. def restartStateTimeout(self, timeout = None):
  127. self.__stateTimeout.start(timeout)
  128. def stateHasTimeout(self):
  129. return self.__stateTimeout.exceed()
  130. class DpSlaveDesc(object):
  131. """Static descriptor data of a DP slave that
  132. is managed by a DPM instance.
  133. """
  134. __slots__ = (
  135. "dpm",
  136. "gsd",
  137. "slaveAddr",
  138. "identNumber",
  139. "name",
  140. "index",
  141. "inputSize",
  142. "outputSize",
  143. "diagPeriod",
  144. "slaveConf",
  145. "userData",
  146. "setPrmTelegram",
  147. "chkCfgTelegram",
  148. )
  149. def __init__(self, slaveConf=None):
  150. self.dpm = None
  151. self.gsd = slaveConf.gsd if slaveConf else None
  152. self.slaveAddr = slaveConf.addr if slaveConf else None
  153. self.identNumber = self.gsd.getIdentNumber() if self.gsd else 0
  154. self.name = slaveConf.name if slaveConf else None
  155. self.index = slaveConf.index if slaveConf else None
  156. self.inputSize = slaveConf.inputSize if slaveConf else 0
  157. self.outputSize = slaveConf.outputSize if slaveConf else 0
  158. self.diagPeriod = slaveConf.diagPeriod if slaveConf else 0
  159. self.slaveConf = slaveConf
  160. self.userData = {} # For use by application code.
  161. # Prepare a Set_Prm telegram.
  162. self.setPrmTelegram = DpTelegram_SetPrm_Req(
  163. da=self.slaveAddr,
  164. sa=None)
  165. self.setPrmTelegram.identNumber = self.identNumber
  166. # Prepare a Chk_Cfg telegram.
  167. self.chkCfgTelegram = DpTelegram_ChkCfg_Req(
  168. da=self.slaveAddr,
  169. sa=None)
  170. def setCfgDataElements(self, cfgDataElements):
  171. """Sets DpCfgDataElement()s from the specified list
  172. in the Chk_Cfg telegram.
  173. """
  174. if cfgDataElements is not None:
  175. self.chkCfgTelegram.clearCfgDataElements()
  176. for cfgDataElement in cfgDataElements:
  177. self.chkCfgTelegram.addCfgDataElement(cfgDataElement)
  178. def setUserPrmData(self, userPrmData):
  179. """Sets the User_Prm_Data of the Set_Prm telegram.
  180. """
  181. if userPrmData is not None:
  182. self.setPrmTelegram.clearUserPrmData()
  183. self.setPrmTelegram.addUserPrmData(userPrmData)
  184. def setSyncMode(self, enabled):
  185. """Enable/disable sync-mode.
  186. Must be called before parameterisation."""
  187. if enabled:
  188. self.setPrmTelegram.stationStatus |= DpTelegram_SetPrm_Req.STA_SYNC
  189. else:
  190. self.setPrmTelegram.stationStatus &= ~DpTelegram_SetPrm_Req.STA_SYNC
  191. def setFreezeMode(self, enabled):
  192. """Enable/disable freeze-mode.
  193. Must be called before parameterisation."""
  194. if enabled:
  195. self.setPrmTelegram.stationStatus |= DpTelegram_SetPrm_Req.STA_FREEZE
  196. else:
  197. self.setPrmTelegram.stationStatus &= ~DpTelegram_SetPrm_Req.STA_FREEZE
  198. def setGroupMask(self, groupMask):
  199. """Assign the slave to one or more groups.
  200. Must be called before parameterisation."""
  201. self.setPrmTelegram.groupIdent = groupMask
  202. def setWatchdog(self, timeoutMS):
  203. """Set the watchdog timeout (in milliseconds).
  204. If timeoutMS is 0, the watchdog is disabled."""
  205. if timeoutMS <= 0:
  206. # Disable watchdog
  207. self.setPrmTelegram.stationStatus &= ~DpTelegram_SetPrm_Req.STA_WD
  208. return
  209. # Enable watchdog
  210. self.setPrmTelegram.stationStatus |= DpTelegram_SetPrm_Req.STA_WD
  211. # Set timeout factors
  212. fact1 = timeoutMS / 10
  213. fact2 = 1
  214. while fact1 > 255:
  215. fact2 *= 2
  216. fact1 /= 2
  217. if fact2 > 255:
  218. raise DpError("Watchdog timeout %d is too big" % timeoutMS)
  219. fact1 = min(255, int(math.ceil(fact1)))
  220. self.setPrmTelegram.wdFact1 = fact1
  221. self.setPrmTelegram.wdFact2 = fact2
  222. def setMasterOutData(self, data):
  223. """Set the master-out-data that will be sent the
  224. next time we are able to send something to that slave.
  225. """
  226. self.dpm._setToSlaveData(self, data)
  227. def getMasterInData(self):
  228. """Get the latest received master-in-data.
  229. Returns None, if there was no received data.
  230. """
  231. return self.dpm._getFromSlaveData(self)
  232. def setOutData(self, outData):
  233. """Deprecated: Don't use this method. Use setMasterOutData() instead."""
  234. self.setMasterOutData(outData)
  235. def getInData(self):
  236. """Deprecated: Don't use this method. Use getMasterInData() instead."""
  237. return self.getMasterInData()
  238. def isConnecting(self):
  239. """Returns True, if the slave is in the process of getting connected/configured,
  240. but is not fully connected, yet.
  241. Otherwise returns False.
  242. """
  243. return self.dpm._slaveIsConnecting(self)
  244. def isConnected(self):
  245. """Returns True, if this slave is fully connected and Data_Exchange
  246. or periodic slave diagnosis is currently running.
  247. Otherwise returns False.
  248. """
  249. return self.dpm._slaveIsConnected(self)
  250. def __repr__(self):
  251. return "DpSlaveDesc(identNumber=%s, slaveAddr=%d)" %\
  252. (intToHex(self.identNumber), self.slaveAddr)
  253. class DpMaster(object):
  254. __slots__ = (
  255. "__runTimer",
  256. "__runCount",
  257. "__haveToken",
  258. "__runNextSlaveIndex",
  259. "__slaveDescs",
  260. "__slaveDescsList",
  261. "__slaveStates",
  262. "__slowDown",
  263. "__slowDownFact",
  264. "__slowDownUntil",
  265. "debug",
  266. "dpTrans",
  267. "dpmClass",
  268. "fdlTrans",
  269. "masterAddr",
  270. "phy",
  271. )
  272. def __init__(self, dpmClass, phy, masterAddr, debug=False):
  273. self.dpmClass = dpmClass
  274. self.phy = phy
  275. self.masterAddr = masterAddr
  276. self.debug = debug
  277. self.__runTimer = monotonic_time()
  278. self.__runCount = 0
  279. # Create the transceivers
  280. self.fdlTrans = FdlTransceiver(self.phy)
  281. self.dpTrans = DpTransceiver(self.fdlTrans, thisIsMaster=True)
  282. mcastSlaveDesc = DpSlaveDesc()
  283. mcastSlaveDesc.slaveAddr = FdlTelegram.ADDRESS_MCAST
  284. mcastSlave = DpSlaveState(self, mcastSlaveDesc)
  285. self.__slaveDescs = {
  286. FdlTelegram.ADDRESS_MCAST : mcastSlaveDesc,
  287. }
  288. self.__slaveStates = {
  289. FdlTelegram.ADDRESS_MCAST : mcastSlave,
  290. }
  291. self.__slaveDescsList = []
  292. self.__runNextSlaveIndex = 0
  293. # Do we have the token?
  294. self.__haveToken = True
  295. self.__slowDown = False
  296. self.__slowDownUntil = monotonic_time()
  297. self.__slowDownFact = 1
  298. def __debugMsg(self, msg):
  299. if self.debug:
  300. print("DPM%d: %s" % (self.dpmClass, msg))
  301. def __errorMsg(self, msg):
  302. print("DPM%d: >ERROR< %s" % (self.dpmClass, msg))
  303. def __masterSlowDown(self):
  304. """A severe communication error occurred.
  305. Slow down the state machine a bit.
  306. """
  307. self.__slowDown = True
  308. self.__slowDownUntil = monotonic_time() + (0.01 * self.__slowDownFact)
  309. self.__debugMsg("Slow down factor = %d" % self.__slowDownFact)
  310. self.__slowDownFact = min(self.__slowDownFact + 1, 10)
  311. def destroy(self):
  312. if self.phy:
  313. self.phy.close()
  314. self.phy = None
  315. def addSlave(self, slaveDesc):
  316. """Register a slave."""
  317. if slaveDesc.inputSize <= 0:
  318. raise DpError("Slave %d: input_size=0 is currently not supported." % (
  319. slaveDesc.slaveAddr))
  320. slaveAddr = slaveDesc.slaveAddr
  321. if slaveAddr in self.__slaveDescs or\
  322. slaveAddr in self.__slaveStates:
  323. raise DpError("Slave %d is already registered." % slaveAddr)
  324. slaveDesc.dpm = self
  325. self.__slaveDescs[slaveAddr] = slaveDesc
  326. self.__slaveStates[slaveAddr] = DpSlaveState(self, slaveDesc)
  327. # Rebuild the slave desc list.
  328. self.__slaveDescsList = [
  329. desc
  330. for addr, desc in sorted(self.__slaveDescs.items(),
  331. key=lambda x: x[0])
  332. if addr != FdlTelegram.ADDRESS_MCAST
  333. ]
  334. self.__runNextSlaveIndex = 0
  335. def getSlaveList(self):
  336. """Get a list of registered DpSlaveDescs, sorted by address.
  337. """
  338. return self.__slaveDescsList
  339. def __send(self, slave, telegram, timeout):
  340. """Asynchronously send a telegram to a slave.
  341. """
  342. slave.pendingReq = telegram
  343. slave.shortAckReceived = False
  344. try:
  345. if FdlTelegram.checkType(telegram):
  346. transceiver = self.fdlTrans
  347. else:
  348. transceiver = self.dpTrans
  349. transceiver.send(fcb=slave.fcb,
  350. telegram=telegram)
  351. except ProfibusError as e:
  352. slave.pendingReq = None
  353. self.__masterSlowDown()
  354. self.__debugMsg(str(e))
  355. return False
  356. self.__slowDownFact = 1
  357. slave.pendingReqTimeout.start(timeout)
  358. return True
  359. def _releaseSlave(self, slave):
  360. self.phy.releaseBus()
  361. def __runSlave_init(self, slave):
  362. if slave.stateJustEntered():
  363. self.__debugMsg("Trying to initialize slave %d..." % (
  364. slave.slaveDesc.slaveAddr))
  365. slave.flushRxQueue()
  366. else:
  367. for telegram in slave.getRxQueue():
  368. if telegram.fc is not None:
  369. slave.pendingReq = None
  370. stype = telegram.fc & FdlTelegram.FC_STYPE_MASK
  371. if telegram.fc & FdlTelegram.FC_REQ:
  372. self.__debugMsg("Slave %d replied with "
  373. "request bit set." %\
  374. slave.slaveDesc.slaveAddr)
  375. elif stype != FdlTelegram.FC_SLAVE:
  376. self.__debugMsg("Device %d is not a slave. "
  377. "Detected type: 0x%02X" % (
  378. slave.slaveDesc.slaveAddr,
  379. stype))
  380. else:
  381. slave.setState(slave.STATE_WDIAG)
  382. return None
  383. else:
  384. self.__debugMsg("Slave %d replied with a "
  385. "weird telegram:\n%s" % str(telegram))
  386. if (not slave.pendingReq or
  387. slave.pendingReqTimeout.exceed()):
  388. # Reset fault debounce counter.
  389. slave.faultDeb.reset()
  390. # Disable the FCB bit.
  391. slave.fcb.enableFCB(False)
  392. ok = self.__send(slave,
  393. telegram=FdlTelegram_FdlStat_Req(
  394. da=slave.slaveDesc.slaveAddr,
  395. sa=self.masterAddr),
  396. timeout=0.01)
  397. if not ok:
  398. self.__debugMsg("FdlStat_Req failed")
  399. return None
  400. return None
  401. def __runSlave_waitDiag(self, slave):
  402. if slave.stateJustEntered():
  403. self.__debugMsg("Requesting Slave_Diag from slave %d..." %\
  404. slave.slaveDesc.slaveAddr)
  405. slave.flushRxQueue()
  406. else:
  407. for telegram in slave.getRxQueue():
  408. if DpTelegram_SlaveDiag_Con.checkType(telegram):
  409. slave.setState(slave.STATE_WPRM)
  410. return None
  411. else:
  412. self.__debugMsg("Received spurious "
  413. "telegram:\n%s" % str(telegram))
  414. if (not slave.pendingReq or
  415. slave.pendingReqTimeout.exceed()):
  416. # Enable the FCB bit.
  417. slave.fcb.enableFCB(True)
  418. # Send a SlaveDiag request
  419. ok = self.__send(slave,
  420. telegram=DpTelegram_SlaveDiag_Req(
  421. da=slave.slaveDesc.slaveAddr,
  422. sa=self.masterAddr),
  423. timeout=0.05)
  424. if not ok:
  425. self.__debugMsg("SlaveDiag_Req failed")
  426. return None
  427. return None
  428. def __runSlave_waitPrm(self, slave):
  429. if slave.stateJustEntered():
  430. self.__debugMsg("Sending Set_Prm to slave %d..." %\
  431. slave.slaveDesc.slaveAddr)
  432. slave.flushRxQueue()
  433. else:
  434. if slave.shortAckReceived:
  435. slave.fcb.handleReply()
  436. slave.setState(slave.STATE_WCFG)
  437. return None
  438. if (not slave.pendingReq or
  439. slave.pendingReqTimeout.exceed()):
  440. # Send a Set_Prm request
  441. slave.slaveDesc.setPrmTelegram.sa = self.masterAddr
  442. ok = self.__send(slave,
  443. telegram=slave.slaveDesc.setPrmTelegram,
  444. timeout=0.05)
  445. if not ok:
  446. self.__debugMsg("Set_Prm failed")
  447. return None
  448. return None
  449. def __runSlave_waitCfg(self, slave):
  450. if slave.stateJustEntered():
  451. self.__debugMsg("Sending Chk_Cfg to slave %d..." %\
  452. slave.slaveDesc.slaveAddr)
  453. slave.flushRxQueue()
  454. else:
  455. if slave.shortAckReceived:
  456. slave.fcb.handleReply()
  457. slave.setState(slave.STATE_WDXRDY)
  458. if (not slave.pendingReq or
  459. slave.pendingReqTimeout.exceed()):
  460. slave.slaveDesc.chkCfgTelegram.sa = self.masterAddr
  461. ok = self.__send(slave,
  462. telegram=slave.slaveDesc.chkCfgTelegram,
  463. timeout=0.05)
  464. if not ok:
  465. self.__debugMsg("Chk_Cfg failed")
  466. return None
  467. return None
  468. def __runSlave_waitDxRdy(self, slave):
  469. if slave.stateJustEntered():
  470. self.__debugMsg("Requesting Slave_Diag (WDXRDY) from slave %d..." %\
  471. slave.slaveDesc.slaveAddr)
  472. slave.flushRxQueue()
  473. else:
  474. for telegram in slave.getRxQueue():
  475. if DpTelegram_SlaveDiag_Con.checkType(telegram):
  476. if telegram.notExist():
  477. self.__errorMsg("Slave %d is not reachable "
  478. "via this line." %\
  479. slave.slaveDesc.slaveAddr)
  480. slave.faultDeb.fault()
  481. if telegram.cfgFault():
  482. self.__errorMsg("Slave %d reports a faulty "
  483. "configuration (Chk_Cfg)." %\
  484. slave.slaveDesc.slaveAddr)
  485. slave.faultDeb.fault()
  486. if telegram.prmFault():
  487. self.__errorMsg("Slave %d reports a faulty "
  488. "parameterization (Set_Prm)." %\
  489. slave.slaveDesc.slaveAddr)
  490. slave.faultDeb.fault()
  491. if telegram.prmReq():
  492. self.__debugMsg("Slave %d requests a new "
  493. "parameterization (Set_Prm)." %\
  494. slave.slaveDesc.slaveAddr)
  495. slave.faultDeb.fault()
  496. if telegram.isNotSupp():
  497. self.__errorMsg("Slave %d replied with "
  498. "\"function not supported\". "
  499. "The parameters should be checked "
  500. "(Set_Prm)." %\
  501. slave.slaveDesc.slaveAddr)
  502. slave.faultDeb.fault()
  503. if telegram.masterLock():
  504. self.__errorMsg("Slave %d is already controlled "
  505. "(locked to) another DP-master." %\
  506. slave.slaveDesc.slaveAddr)
  507. slave.faultDeb.fault()
  508. if not telegram.hasOnebit():
  509. self.__debugMsg("Slave %d diagnostic "
  510. "always-one-bit is zero." %\
  511. slave.slaveDesc.slaveAddr)
  512. slave.faultDeb.fault()
  513. if telegram.hasExtDiag():
  514. pass#TODO turn on red DIAG-LED
  515. slave.faultDeb.fault()
  516. if telegram.isReadyDataEx():
  517. slave.setState(slave.STATE_DX)
  518. return None
  519. if telegram.needsNewPrmCfg():
  520. slave.setState(slave.STATE_INIT)
  521. return None
  522. break
  523. else:
  524. self.__debugMsg("Received spurious "
  525. "telegram:\n%s" % str(telegram))
  526. slave.faultDeb.fault()
  527. if (not slave.pendingReq or
  528. slave.pendingReqTimeout.exceed()):
  529. ok = self.__send(slave,
  530. telegram=DpTelegram_SlaveDiag_Req(
  531. da=slave.slaveDesc.slaveAddr,
  532. sa=self.masterAddr),
  533. timeout=0.05)
  534. if not ok:
  535. self.__debugMsg("SlaveDiag_Req failed")
  536. slave.faultDeb.fault()
  537. return None
  538. self.__checkFaultDeb(slave, False)
  539. return None
  540. def __runSlave_dataExchange(self, slave):
  541. dataExInData = None
  542. if slave.stateJustEntered():
  543. self.__debugMsg("%sRunning Data_Exchange with slave %d..." % (
  544. "" if slave.dxCycleRunning else "Initialization finished. ",
  545. slave.slaveDesc.slaveAddr))
  546. slave.flushRxQueue()
  547. slave.faultDeb.ok()
  548. slave.dxStartTime = monotonic_time()
  549. slave.dxCycleRunning = True
  550. slave.dxCount = 0
  551. slaveOutputSize = slave.slaveDesc.outputSize
  552. if slave.pendingReq:
  553. for telegram in slave.getRxQueue():
  554. if slaveOutputSize == 0:
  555. # This slave should not send any data.
  556. self.__debugMsg("Ignoring telegram in "
  557. "DataExchange with slave %d:\n%s" %(
  558. slave.slaveDesc.slaveAddr, str(telegram)))
  559. slave.faultDeb.fault()
  560. continue
  561. else:
  562. # This slave is supposed to send some data.
  563. # Get it.
  564. if not DpTelegram_DataExchange_Con.checkType(telegram):
  565. self.__debugMsg("Ignoring telegram in "
  566. "DataExchange with slave %d:\n%s" %(
  567. slave.slaveDesc.slaveAddr, str(telegram)))
  568. slave.faultDeb.fault()
  569. continue
  570. resFunc = telegram.fc & FdlTelegram.FC_RESFUNC_MASK
  571. if resFunc in (FdlTelegram.FC_DH, FdlTelegram.FC_RDH):
  572. self.__debugMsg("Slave %d requested diagnostics." %\
  573. slave.slaveDesc.slaveAddr)
  574. slave.setState(slave.STATE_WDXRDY, 0.2)
  575. elif resFunc == FdlTelegram.FC_RS:
  576. raise DpError("Service not active "
  577. "on slave %d" % slave.slaveDesc.slaveAddr)
  578. dataExInData = telegram.getDU()
  579. if (dataExInData is not None or
  580. (slaveOutputSize == 0 and slave.shortAckReceived)):
  581. # We received some data or an ACK (input-only slave).
  582. slave.pendingReq = None
  583. slave.faultDeb.ok()
  584. slave.restartStateTimeout()
  585. self._releaseSlave(slave)
  586. else:
  587. # No data or ACK received from slave.
  588. if slave.pendingReqTimeout.exceed():
  589. self.__debugMsg("Data_Exchange timeout with slave %d" % (
  590. slave.slaveDesc.slaveAddr))
  591. slave.faultDeb.fault()
  592. slave.pendingReq = None
  593. else:
  594. diagPeriod = slave.slaveDesc.diagPeriod
  595. if diagPeriod > 0 and slave.dxCount >= diagPeriod:
  596. # The input-only slave shall periodically be diagnosed.
  597. # Go to diagnostic state.
  598. slave.setState(slave.STATE_WDXRDY, 0.2)
  599. else:
  600. # Send the out data telegram, if any.
  601. toSlaveData = slave.toSlaveData
  602. if toSlaveData is not None:
  603. if slave.slaveDesc.inputSize == 0:
  604. self.__debugMsg("Got data for slave, "
  605. "but slave does not expect any input data.")
  606. else:
  607. ok = self.__send(slave,
  608. telegram=DpTelegram_DataExchange_Req(
  609. da=slave.slaveDesc.slaveAddr,
  610. sa=self.masterAddr,
  611. du=toSlaveData),
  612. timeout=0.1)
  613. if ok:
  614. # We sent it. Reset the data.
  615. slave.toSlaveData = None
  616. slave.dxCount = min(slave.dxCount + 1, 0x3FFFFFFF)
  617. else:
  618. self.__debugMsg("DataExchange_Req failed")
  619. slave.faultDeb.fault()
  620. if self.__checkFaultDeb(slave, True):
  621. return None
  622. return dataExInData
  623. def __checkFaultDeb(self, slave, inDataExchange):
  624. faultCount = slave.faultDeb.get()
  625. if faultCount >= 5:
  626. # communication lost
  627. self.__debugMsg("Communication lost in Data_Exchange or Slave_Diag.")
  628. slave.setState(slave.STATE_INIT)
  629. return True
  630. elif (faultCount >= 3 and
  631. inDataExchange and
  632. (monotonic_time() >= slave.dxStartTime + 0.2 or slave.slaveDesc.outputSize == 0)):
  633. # Diagnose the slave
  634. self.__debugMsg("Many errors in Data_Exchange. "
  635. "Requesting diagnostic information...")
  636. slave.setState(slave.STATE_WDXRDY, 0.2)
  637. return True
  638. return False
  639. __slaveStateHandlers = {
  640. DpSlaveState.STATE_INIT : __runSlave_init,
  641. DpSlaveState.STATE_WDIAG : __runSlave_waitDiag,
  642. DpSlaveState.STATE_WPRM : __runSlave_waitPrm,
  643. DpSlaveState.STATE_WCFG : __runSlave_waitCfg,
  644. DpSlaveState.STATE_WDXRDY : __runSlave_waitDxRdy,
  645. DpSlaveState.STATE_DX : __runSlave_dataExchange,
  646. }
  647. def __runSlave(self, slave):
  648. self.__pollRx()
  649. if not self.__haveToken:
  650. return None
  651. if slave.stateHasTimeout():
  652. self.__debugMsg("State machine timeout! "
  653. "Trying to re-initializing slave %d..." %\
  654. slave.slaveDesc.slaveAddr)
  655. slave.setState(slave.STATE_INIT)
  656. dataExInData = None
  657. else:
  658. handler = self.__slaveStateHandlers[slave.getState()]
  659. dataExInData = handler(self, slave)
  660. if slave.stateIsChanging():
  661. self.__debugMsg("slave[%02X].state --> '%s'" % (
  662. slave.slaveDesc.slaveAddr,
  663. slave.state2name[slave.getNextState()]))
  664. slave.applyState()
  665. return dataExInData
  666. def __pollRx(self):
  667. try:
  668. ok, telegram = self.dpTrans.poll()
  669. except ProfibusError as e:
  670. self.__debugMsg("RX error: %s" % str(e))
  671. return
  672. if ok and telegram:
  673. if FdlTelegram_token.checkType(telegram):
  674. pass#TODO handle token
  675. elif FdlTelegram_ack.checkType(telegram):
  676. for addr, slave in self.__slaveStates.items():
  677. if addr != FdlTelegram.ADDRESS_MCAST:
  678. slave.shortAckReceived = True
  679. elif telegram.da == FdlTelegram.ADDRESS_MCAST:
  680. self.__handleMcastTelegram(telegram)
  681. elif telegram.da == self.masterAddr:
  682. if telegram.sa in self.__slaveStates:
  683. slave = self.__slaveStates[telegram.sa]
  684. slave.rxQueue.append(telegram)
  685. slave.fcb.handleReply()
  686. else:
  687. self.__debugMsg("Received telegram from "
  688. "unknown station %d:\n%s" %(
  689. telegram.sa, str(telegram)))
  690. else:
  691. self.__debugMsg("Received telegram for "
  692. "foreign station:\n%s" % str(telegram))
  693. else:
  694. if telegram:
  695. self.__debugMsg("Received corrupt "
  696. "telegram:\n%s" % str(telegram))
  697. def __handleMcastTelegram(self, telegram):
  698. self.__debugMsg("Received multicast telegram:\n%s" % str(telegram))
  699. pass#TODO
  700. def run(self):
  701. """Run the DP-Master state machine.
  702. """
  703. if self.debug:
  704. self.__runCount += 1
  705. now = monotonic_time()
  706. if now >= self.__runTimer + 10.0:
  707. cps = self.__runCount / (now - self.__runTimer)
  708. self.__debugMsg("State machine calls: "
  709. "%.1f /s = %.3f s/call" % (
  710. cps, 1.0 / cps))
  711. self.__runTimer = now
  712. self.__runCount = 0
  713. if self.__slowDown:
  714. # Master slowdown is active.
  715. # Do not run state machine until the end of the slowdown.
  716. if monotonic_time() < self.__slowDownUntil:
  717. return None
  718. self.__slowDown = False
  719. slaveDescsList = self.__slaveDescsList
  720. runNextSlaveIndex = self.__runNextSlaveIndex
  721. if not slaveDescsList:
  722. return None
  723. slaveDesc = slaveDescsList[runNextSlaveIndex]
  724. self.__runNextSlaveIndex = (runNextSlaveIndex + 1) % len(slaveDescsList)
  725. slave = self.__slaveStates[slaveDesc.slaveAddr]
  726. fromSlaveData = self.__runSlave(slave)
  727. if (fromSlaveData is not None and
  728. len(fromSlaveData) != slaveDesc.outputSize):
  729. self.__errorMsg("Slave %d: The received data size (%d bytes) "
  730. "does not match the slave's configured output_size (%d bytes)." % (
  731. slaveDesc.slaveAddr,
  732. len(fromSlaveData),
  733. slaveDesc.outputSize))
  734. slave.faultDeb.fault()
  735. fromSlaveData = None
  736. slave.fromSlaveData = fromSlaveData
  737. return slaveDesc
  738. def _setToSlaveData(self, slaveDesc, data):
  739. """Set the master-out-data that will be sent the
  740. next time we are able to send something to that slave.
  741. """
  742. if (data is not None and
  743. len(data) != slaveDesc.inputSize):
  744. raise DpError("Slave %d: The setMasterOutData() data size (%d bytes) "
  745. "does not match the slave's configured input_size (%d bytes)." % (
  746. slaveDesc.slaveAddr,
  747. len(data),
  748. slaveDesc.inputSize))
  749. slave = self.__slaveStates[slaveDesc.slaveAddr]
  750. slave.toSlaveData = data
  751. def _getFromSlaveData(self, slaveDesc):
  752. """Get the latest received master-in-data.
  753. Returns None, if there was no received data.
  754. """
  755. slave = self.__slaveStates[slaveDesc.slaveAddr]
  756. fromSlaveData = slave.fromSlaveData
  757. slave.fromSlaveData = None
  758. return fromSlaveData
  759. def _slaveIsConnecting(self, slaveDesc):
  760. slave = self.__slaveStates[slaveDesc.slaveAddr]
  761. return (not slave.dxCycleRunning and
  762. slave.getState() != DpSlaveState.STATE_INIT)
  763. def _slaveIsConnected(self, slaveDesc):
  764. slave = self.__slaveStates[slaveDesc.slaveAddr]
  765. return slave.dxCycleRunning
  766. def initialize(self):
  767. """Initialize the DPM."""
  768. # Initialize the RX filter
  769. self.fdlTrans.setRXFilter([self.masterAddr,
  770. FdlTelegram.ADDRESS_MCAST])
  771. # Free memory
  772. gc.collect()
  773. def __syncFreezeHelper(self, groupMask, controlCommand):
  774. slave = self.__slaveStates[FdlTelegram.ADDRESS_MCAST]
  775. globCtl = DpTelegram_GlobalControl(da = FdlTelegram.ADDRESS_MCAST,
  776. sa = self.masterAddr)
  777. globCtl.controlCommand |= controlCommand
  778. globCtl.groupSelect = groupMask & 0xFF
  779. self.dpTrans.send(fcb = slave.fcb,
  780. telegram = globCtl)
  781. def syncMode(self, groupMask):
  782. """Set SYNC-mode on the specified groupMask.
  783. If groupMask is 0, all slaves are addressed."""
  784. self.__syncFreezeHelper(groupMask, DpTelegram_GlobalControl.CCMD_SYNC)
  785. def syncModeCancel(self, groupMask):
  786. """Cancel SYNC-mode on the specified groupMask.
  787. If groupMask is 0, all slaves are addressed."""
  788. self.__syncFreezeHelper(groupMask, DpTelegram_GlobalControl.CCMD_UNSYNC)
  789. def freezeMode(self, groupMask):
  790. """Set FREEZE-mode on the specified groupMask.
  791. If groupMask is 0, all slaves are addressed."""
  792. self.__syncFreezeHelper(groupMask, DpTelegram_GlobalControl.CCMD_FREEZE)
  793. def freezeModeCancel(self, groupMask):
  794. """Cancel FREEZE-mode on the specified groupMask.
  795. If groupMask is 0, all slaves are addressed."""
  796. self.__syncFreezeHelper(groupMask, DpTelegram_GlobalControl.CCMD_UNFREEZE)
  797. class DPM1(DpMaster):
  798. def __init__(self, phy, masterAddr, debug=False):
  799. DpMaster.__init__(self, dpmClass=1, phy=phy,
  800. masterAddr=masterAddr,
  801. debug=debug)
  802. class DPM2(DpMaster):
  803. def __init__(self, phy, masterAddr, debug=False):
  804. DpMaster.__init__(self, dpmClass=2, phy=phy,
  805. masterAddr=masterAddr,
  806. debug=debug)