dp.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. # -*- coding: utf-8 -*-
  2. #
  3. # PROFIBUS DP - Layer 7
  4. #
  5. # Copyright (c) 2013-2020 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.util import *
  14. __all__ = [
  15. "DpError",
  16. "DpTransceiver",
  17. "DpTelegram",
  18. "DpTelegram_DataExchange_Req",
  19. "DpTelegram_DataExchange_Con",
  20. "DpTelegram_SlaveDiag_Req",
  21. "DpTelegram_SlaveDiag_Con",
  22. "DpTelegram_SetPrm_Req",
  23. "DpCfgDataElement",
  24. "DpTelegram_ChkCfg_Req",
  25. "DpTelegram_GetCfg_Req",
  26. "DpTelegram_GetCfg_Con",
  27. "DpTelegram_GlobalControl",
  28. ]
  29. class DpError(ProfibusError):
  30. __slots__ = (
  31. )
  32. class DpTransceiver(object):
  33. __slots__ = (
  34. "fdlTrans",
  35. "thisIsMaster",
  36. )
  37. def __init__(self, fdlTrans, thisIsMaster):
  38. self.fdlTrans = fdlTrans
  39. self.thisIsMaster = thisIsMaster
  40. def poll(self, timeout=0.0):
  41. retTelegram = None
  42. ok, fdlTelegram = self.fdlTrans.poll(timeout)
  43. if ok and fdlTelegram:
  44. if fdlTelegram.sd in (FdlTelegram.SD1,
  45. FdlTelegram.SD2,
  46. FdlTelegram.SD3,):
  47. retTelegram = DpTelegram.fromFdlTelegram(
  48. fdlTelegram, self.thisIsMaster)
  49. elif fdlTelegram.sd in (FdlTelegram.SC,
  50. FdlTelegram.SD4,):
  51. retTelegram = fdlTelegram
  52. else:
  53. ok = False
  54. return (ok, retTelegram)
  55. # Send a DpTelegram.
  56. def send(self, fcb, telegram):
  57. self.fdlTrans.send(fcb, telegram.toFdlTelegram())
  58. class DpTelegram(object):
  59. # Source Service Access Point number
  60. SSAP_MS2 = 50 # DPM2 to slave
  61. SSAP_MS1 = 51 # DPM1 to slave
  62. SSAP_MM = 54 # Master to master
  63. SSAP_MS0 = 62 # Master to slave
  64. # Destination Service Access Point number
  65. DSAP_RESOURCE_MAN = 49
  66. DSAP_ALARM = 50
  67. DSAP_SERVER = 51
  68. DSAP_EXT_USER_PRM = 53
  69. DSAP_SET_SLAVE_ADR = 55
  70. DSAP_RD_INP = 56
  71. DSAP_RD_OUTP = 57
  72. DSAP_GLOBAL_CONTROL = 58
  73. DSAP_GET_CFG = 59
  74. DSAP_SLAVE_DIAG = 60
  75. DSAP_SET_PRM = 61
  76. DSAP_CHK_CFG = 62
  77. __slots__ = (
  78. "da",
  79. "sa",
  80. "fc",
  81. "dsap",
  82. "ssap",
  83. )
  84. def __init__(self, da, sa, fc, dsap=None, ssap=None):
  85. self.da = da
  86. self.sa = sa
  87. self.fc = fc
  88. self.dsap = dsap
  89. self.ssap = ssap
  90. def __repr__(self):
  91. return ("DpTelegram(da=%s, sa=%s, fc=%s, "
  92. "dsap=%s, ssap=%s, du=%s)" % (
  93. intToHex(self.da),
  94. intToHex(self.sa),
  95. intToHex(self.fc),
  96. intToHex(self.dsap),
  97. intToHex(self.ssap),
  98. bytesToHex(self.getDU())))
  99. def toFdlTelegram(self):
  100. du = self.getDU()
  101. dae = bytearray()
  102. if self.dsap is not None:
  103. dae.append(self.dsap)
  104. sae = bytearray()
  105. if self.ssap is not None:
  106. sae.append(self.ssap)
  107. le = len(du) + len(dae) + len(sae)
  108. if le == 0:
  109. return FdlTelegram_stat0(
  110. da=self.da, sa=self.sa, fc=self.fc)
  111. elif le == 8:
  112. return FdlTelegram_stat8(
  113. da=self.da, sa=self.sa, fc=self.fc,
  114. dae=dae, sae=sae, du=du)
  115. else:
  116. return FdlTelegram_var(
  117. da=self.da, sa=self.sa, fc=self.fc,
  118. dae=dae, sae=sae, du=du)
  119. # Extract the SSAP/DSAP from SAE/DAE
  120. @classmethod
  121. def extractSAP(cls, ae):
  122. if ae:
  123. for aeByte in ae:
  124. if not (aeByte & 0x40):
  125. return aeByte & 0x3F
  126. return None
  127. # Extract the segment address from SAE/DAE
  128. @classmethod
  129. def extractSegmentAddr(cls, ae):
  130. if ae:
  131. for aeByte in ae:
  132. if aeByte & 0x40:
  133. return aeByte & 0x3F
  134. return None
  135. # Create a DP telegram from an FDL telegram.
  136. # If thisIsMaster is True, the local station is a master.
  137. @classmethod
  138. def fromFdlTelegram(cls, fdl, thisIsMaster):
  139. dsap, ssap = cls.extractSAP(fdl.dae), cls.extractSAP(fdl.sae)
  140. # Handle telegrams without SSAP/DSAP
  141. if not dsap:
  142. if ssap:
  143. raise DpError("Telegram with SSAP, but without DSAP")
  144. if fdl.fc & FdlTelegram.FC_REQ:
  145. return DpTelegram_DataExchange_Req.fromFdlTelegram(fdl)
  146. else:
  147. return DpTelegram_DataExchange_Con.fromFdlTelegram(fdl)
  148. if not ssap:
  149. raise DpError("Telegram with DSAP, but without SSAP")
  150. # Handle telegrams with SSAP/DSAP
  151. if thisIsMaster:
  152. if dsap == DpTelegram.SSAP_MS0:
  153. if ssap == DpTelegram.DSAP_SLAVE_DIAG:
  154. return DpTelegram_SlaveDiag_Con.fromFdlTelegram(fdl)
  155. elif ssap == DpTelegram.DSAP_GET_CFG:
  156. return DpTelegram_GetCfg_Con.fromFdlTelegram(fdl)
  157. else:
  158. raise DpError("Unknown SSAP: %d" % ssap)
  159. else:
  160. raise DpError("Unknown DSAP: %d" % dsap)
  161. else:
  162. if ssap == DpTelegram.SSAP_MS0:
  163. if dsap == DpTelegram.DSAP_SLAVE_DIAG:
  164. return DpTelegram_SlaveDiag_Req.fromFdlTelegram(fdl)
  165. elif dsap == DpTelegram.DSAP_SET_PRM:
  166. return DpTelegram_SetPrm_Req.fromFdlTelegram(fdl)
  167. elif dsap == DpTelegram.DSAP_CHK_CFG:
  168. return DpTelegram_ChkCfg_Req.fromFdlTelegram(fdl)
  169. else:
  170. raise DpError("Unknown DSAP: %d" % dsap)
  171. else:
  172. raise DpError("Unknown SSAP: %d" % ssap)
  173. # Get Data-Unit.
  174. # This function is overloaded in subclasses.
  175. def getDU(self):
  176. return b""
  177. @classmethod
  178. def checkType(cls, telegram):
  179. return isinstance(telegram, cls)
  180. class _DataExchange_Common(DpTelegram):
  181. __slots__ = (
  182. "du",
  183. )
  184. def __init__(self, da, sa, fc, du):
  185. DpTelegram.__init__(self,
  186. da=da, sa=sa, fc=fc)
  187. self.du = bytearray(du)
  188. def appendData(self, data):
  189. if not self.du:
  190. self.du = bytearray()
  191. self.du.append(data)
  192. def getDU(self):
  193. return bytearray(self.du)
  194. @classmethod
  195. def fromFdlTelegram(cls, fdl):
  196. dp = cls(da=fdl.da,
  197. sa=fdl.sa,
  198. fc=fdl.fc,
  199. du=fdl.du if fdl.du else b"")
  200. return dp
  201. class DpTelegram_DataExchange_Req(_DataExchange_Common):
  202. __slots__ = (
  203. )
  204. def __init__(self, da, sa,
  205. fc=FdlTelegram.FC_SRD_HI |
  206. FdlTelegram.FC_REQ,
  207. du=b""):
  208. _DataExchange_Common.__init__(self,
  209. da=da, sa=sa, fc=fc, du=du)
  210. class DpTelegram_DataExchange_Con(_DataExchange_Common):
  211. __slots__ = (
  212. )
  213. def __init__(self, da, sa,
  214. fc=FdlTelegram.FC_DL,
  215. du=()):
  216. _DataExchange_Common.__init__(self,
  217. da=da, sa=sa, fc=fc, du=du)
  218. class DpTelegram_SlaveDiag_Req(DpTelegram):
  219. __slots__ = (
  220. )
  221. def __init__(self, da, sa,
  222. fc=FdlTelegram.FC_SRD_HI |
  223. FdlTelegram.FC_REQ,
  224. dsap=DpTelegram.DSAP_SLAVE_DIAG,
  225. ssap=DpTelegram.SSAP_MS0):
  226. DpTelegram.__init__(self, da=da, sa=sa, fc=fc,
  227. dsap=dsap, ssap=ssap)
  228. @classmethod
  229. def fromFdlTelegram(cls, fdl):
  230. dp = cls(da=fdl.da,
  231. sa=fdl.sa,
  232. fc=fdl.fc,
  233. dsap=cls.extractSAP(fdl.dae),
  234. ssap=cls.extractSAP(fdl.sae))
  235. return dp
  236. class DpTelegram_SlaveDiag_Con(DpTelegram):
  237. # Flags byte 0
  238. B0_STANOEX = 0x01 # Station_Non_Existent
  239. B0_STANORDY = 0x02 # Station_Not_Reay
  240. B0_CFGFLT = 0x04 # Cfg_Fault
  241. B0_EXTDIAG = 0x08 # Ext_Diag
  242. B0_NOSUPP = 0x10 # Not_Supported
  243. B0_INVALSR = 0x20 # Invalid_Slave_Response
  244. B0_PRMFLT = 0x40 # Prm_Fault
  245. B0_MLOCK = 0x80 # Master_Lock
  246. # Flags byte 1
  247. B1_PRMREQ = 0x01 # Prm_Req
  248. B1_SDIAG = 0x02 # Stat_Diag
  249. B1_ONE = 0x04 # Always 1
  250. B1_WD = 0x08 # Wd_On
  251. B1_FREEZE = 0x10 # Freeze_Mode
  252. B1_SYNC = 0x20 # Sync_Mode
  253. B1_RES = 0x40 # Reserved
  254. B1_DEAC = 0x80 # Deactivated
  255. # Flags byte 2
  256. B2_EXTDIAGOVR = 0x80 # Ext_Diag_Overflow
  257. __slots__ = (
  258. "b0",
  259. "b1",
  260. "b2",
  261. "masterAddr",
  262. "identNumber",
  263. )
  264. def __init__(self, da, sa, fc=FdlTelegram.FC_DL,
  265. dsap=DpTelegram.SSAP_MS0,
  266. ssap=DpTelegram.DSAP_SLAVE_DIAG):
  267. DpTelegram.__init__(self, da=da, sa=sa, fc=fc,
  268. dsap=dsap, ssap=ssap)
  269. self.b0 = 0
  270. self.b1 = 0
  271. self.b2 = 0
  272. self.masterAddr = 255
  273. self.identNumber = 0
  274. def __repr__(self):
  275. return ("DpTelegram_SlaveDiag_Con(da=%s, sa=%s, fc=%s, "
  276. "dsap=%s, ssap=%s, "
  277. "b0=%s, b1=%s, b2=%s, masterAddr=%s, identNumber=%s)" % (
  278. intToHex(self.da),
  279. intToHex(self.sa),
  280. intToHex(self.fc),
  281. intToHex(self.dsap),
  282. intToHex(self.ssap),
  283. intToHex(self.b0),
  284. intToHex(self.b1),
  285. intToHex(self.b2),
  286. intToHex(self.masterAddr),
  287. intToHex(self.identNumber)))
  288. @classmethod
  289. def fromFdlTelegram(cls, fdl):
  290. dp = cls(da=fdl.da,
  291. sa=fdl.sa,
  292. fc=fdl.fc,
  293. dsap=cls.extractSAP(fdl.dae),
  294. ssap=cls.extractSAP(fdl.sae))
  295. try:
  296. dp.b0 = fdl.du[0]
  297. dp.b1 = fdl.du[1]
  298. dp.b2 = fdl.du[2]
  299. dp.masterAddr = fdl.du[3]
  300. dp.identNumber = (fdl.du[4] << 8) | fdl.du[5]
  301. except IndexError:
  302. raise DpError("Invalid Slave_Diag telegram format")
  303. return dp
  304. def getDU(self):
  305. return bytearray((self.b0,
  306. self.b1,
  307. self.b2,
  308. self.masterAddr,
  309. (self.identNumber >> 8) & 0xFF,
  310. self.identNumber & 0xFF))
  311. def notExist(self):
  312. return (self.b0 & self.B0_STANOEX) != 0
  313. def notReady(self):
  314. return (self.b0 & self.B0_STANORDY) != 0
  315. def cfgFault(self):
  316. return (self.b0 & self.B0_CFGFLT) != 0
  317. def hasExtDiag(self):
  318. return (self.b0 & self.B0_EXTDIAG) != 0
  319. def isNotSupp(self):
  320. return (self.b0 & self.B0_NOSUPP) != 0
  321. def prmFault(self):
  322. return (self.b0 & self.B0_PRMFLT) != 0
  323. def masterLock(self):
  324. return (self.b0 & self.B0_MLOCK) != 0
  325. def hasOnebit(self):
  326. return (self.b1 & self.B1_ONE) != 0
  327. def prmReq(self):
  328. return (self.b1 & self.B1_PRMREQ) != 0
  329. def needsNewPrmCfg(self):
  330. return ((self.b0 & self.B0_CFGFLT) != 0 or
  331. (self.b0 & self.B0_PRMFLT) != 0 or
  332. (self.b1 & self.B1_PRMREQ) != 0)
  333. def isReadyDataEx(self):
  334. return ((self.b0 & (self.B0_STANOEX |
  335. self.B0_STANORDY |
  336. self.B0_CFGFLT |
  337. self.B0_PRMFLT)) == 0 and
  338. (self.b1 & (self.B1_PRMREQ)) == 0)
  339. class DpTelegram_SetPrm_Req(DpTelegram):
  340. # Station status
  341. STA_WD = 0x08 # WD_On
  342. STA_FREEZE = 0x10 # Freeze_Req
  343. STA_SYNC = 0x20 # Sync_Req
  344. STA_UNLOCK = 0x40 # Unlock_Req
  345. STA_LOCK = 0x80 # Lock_Req
  346. # First DPv1 User_Prm_Data byte. (DPv1 or later only)
  347. DPV1PRM0_R0 = 0x01 # Reserved bit 0
  348. DPV1PRM0_R1 = 0x02 # Reserved bit 1
  349. DPV1PRM0_WD1MS = 0x04 # 1 ms warchdog base.
  350. DPV1PRM0_R3 = 0x08 # Reserved bit 3
  351. DPV1PRM0_R4 = 0x10 # Reserved bit 4
  352. DPV1PRM0_PUBL = 0x20 # Run as publisher
  353. DPV1PRM0_FAILSAFE = 0x40 # Fail_Safe mode
  354. DPV1PRM0_V1MODE = 0x80 # DPv1 mode enable
  355. # Second DPv1 User_Prm_Data byte. (DPv1 or later only)
  356. DPV1PRM1_REDCFG = 0x01 # Reduced Chk_Cfg
  357. DPV1PRM1_R1 = 0x02 # Reserved bit 1
  358. DPV1PRM1_ALRMUPD = 0x04 # Alarm: update
  359. DPV1PRM1_ALRMSTAT = 0x08 # Alarm: status
  360. DPV1PRM1_ALRMVEND = 0x10 # Alarm: vendor specific
  361. DPV1PRM1_ALRMDIAG = 0x20 # Alarm: diagnostic
  362. DPV1PRM1_ALRMPROC = 0x40 # Alarm: process
  363. DPV1PRM1_ALRMPLUG = 0x80 # Alarm: pull-plug
  364. # Third DPv1 User_Prm_Data byte. (DPv1 or later only)
  365. DPV1PRM2_ALRMCNT_MASK = 0x07 # Alarm count mask
  366. DPV1PRM2_ALRMCNT1 = 0x00 # 1 alarm in total
  367. DPV1PRM2_ALRMCNT2 = 0x01 # 2 alarms in total
  368. DPV1PRM2_ALRMCNT4 = 0x02 # 4 alarms in total
  369. DPV1PRM2_ALRMCNT8 = 0x03 # 8 alarms in total
  370. DPV1PRM2_ALRMCNT12 = 0x04 # 12 alarms in total
  371. DPV1PRM2_ALRMCNT16 = 0x05 # 16 alarms in total
  372. DPV1PRM2_ALRMCNT24 = 0x06 # 24 alarms in total
  373. DPV1PRM2_ALRMCNT32 = 0x07 # 32 alarms in total
  374. DPV1PRM2_PRMBLK = 0x08 # Parameter block follows
  375. DPV1PRM2_ISO = 0x10 # Isochronous mode
  376. DPV1PRM2_R5 = 0x20 # Reserved bit 5
  377. DPV1PRM2_R6 = 0x40 # Reserved bit 6
  378. DPV1PRM2_REDUN = 0x80 # Redundancy commands on
  379. __slots__ = (
  380. "stationStatus",
  381. "wdFact1",
  382. "wdFact2",
  383. "minTSDR",
  384. "identNumber",
  385. "groupIdent",
  386. "userPrmData",
  387. )
  388. def __init__(self, da, sa,
  389. fc=FdlTelegram.FC_SRD_HI |
  390. FdlTelegram.FC_REQ,
  391. dsap=DpTelegram.DSAP_SET_PRM,
  392. ssap=DpTelegram.SSAP_MS0):
  393. DpTelegram.__init__(self, da=da, sa=sa, fc=fc,
  394. dsap=dsap, ssap=ssap)
  395. self.stationStatus = self.STA_LOCK # Station_Status
  396. self.wdFact1 = 1 # WD_Fact_1
  397. self.wdFact2 = 1 # WD_Fact_2
  398. self.minTSDR = 0 # min_Tsdr (0 = no change)
  399. self.identNumber = 0 # Ident_Number
  400. self.groupIdent = 0 # Group_Ident (Lock_Req must be set)
  401. self.clearUserPrmData()
  402. def __repr__(self):
  403. return ("DpTelegram_SetPrm_Req(da=%s, sa=%s, fc=%s, "
  404. "dsap=%s, ssap=%s, "
  405. "stationStatus=%s, wdFact1=%s, wdFact2=%s, "
  406. "minTSDR=%s, identNumber=%s, groupIdent=%s, "
  407. "userPrmData=%s)" % (
  408. intToHex(self.da),
  409. intToHex(self.sa),
  410. intToHex(self.fc),
  411. intToHex(self.dsap),
  412. intToHex(self.ssap),
  413. intToHex(self.stationStatus),
  414. intToHex(self.wdFact1),
  415. intToHex(self.wdFact2),
  416. intToHex(self.minTSDR),
  417. intToHex(self.identNumber),
  418. intToHex(self.groupIdent),
  419. bytesToHex(self.userPrmData)))
  420. @classmethod
  421. def fromFdlTelegram(cls, fdl):
  422. dp = cls(da=fdl.da,
  423. sa=fdl.sa,
  424. fc=fdl.fc,
  425. dsap=cls.extractSAP(fdl.dae),
  426. ssap=cls.extractSAP(fdl.sae))
  427. try:
  428. du = fdl.du
  429. dp.stationStatus = du[0]
  430. dp.wdFact1 = du[1]
  431. dp.wdFact2 = du[2]
  432. dp.minTSDR = du[3]
  433. dp.identNumber = (du[4] << 8) | du[5]
  434. dp.groupIdent = du[6]
  435. dp.userPrmData = du[7:]
  436. except IndexError:
  437. raise DpError("Invalid SetPrm telegram format")
  438. return dp
  439. def clearUserPrmData(self):
  440. self.userPrmData = bytearray()
  441. def addUserPrmData(self, data):
  442. assert isinstance(data, (bytes, bytearray))
  443. self.userPrmData.extend(data)
  444. def getDU(self):
  445. du = bytearray((self.stationStatus,
  446. self.wdFact1,
  447. self.wdFact2,
  448. self.minTSDR,
  449. (self.identNumber >> 8) & 0xFF,
  450. self.identNumber & 0xFF,
  451. self.groupIdent))
  452. du.extend(self.userPrmData)
  453. return du
  454. class DpCfgDataElement(object):
  455. # Identifier
  456. ID_LEN_MASK = 0x0F # Length of data
  457. ID_TYPE_MASK = 0x30
  458. ID_TYPE_SPEC = 0x00 # Specific formats
  459. ID_TYPE_IN = 0x10 # Input
  460. ID_TYPE_OUT = 0x20 # Output
  461. ID_TYPE_INOUT = 0x30 # Input/output
  462. ID_LEN_WORDS = 0x40 # Word structure
  463. ID_CON_WHOLE = 0x80 # Consistency over whole length
  464. # Special identifier
  465. ID_SPEC_MASK = 0xC0
  466. ID_SPEC_FREE = 0x00 # Free place
  467. ID_SPEC_IN = 0x40 # 1 byte for input follows
  468. ID_SPEC_OUT = 0x80 # 1 byte for output follows
  469. ID_SPEC_INOUT = 0xC0 # 1 b for output and 1 b for input follows
  470. # Length byte
  471. LEN_COUNT = 0x3F # Length of inputs/outputs
  472. LEN_WORDS = 0x40 # Word structure
  473. LEN_CON_WHOLE = 0x80 # Consistency over whole length
  474. __slots__ = (
  475. "identifier",
  476. "lengthBytes",
  477. )
  478. def __init__(self, identifier=0, lengthBytes=b""):
  479. self.identifier = identifier
  480. self.lengthBytes = lengthBytes
  481. assert isinstance(lengthBytes, (bytes, bytearray))
  482. def __repr__(self):
  483. return ("DpCfgDataElement(identifier=%s, length=%s)" % (
  484. intToHex(self.identifier),
  485. bytesToHex(self.lengthBytes)))
  486. def getDU(self):
  487. du = bytearray((self.identifier,))
  488. du.extend(self.lengthBytes)
  489. return du
  490. class DpTelegram_ChkCfg_Req(DpTelegram):
  491. __slots__ = (
  492. "cfgData",
  493. )
  494. def __init__(self, da, sa,
  495. fc=FdlTelegram.FC_SRD_HI |
  496. FdlTelegram.FC_REQ,
  497. dsap=DpTelegram.DSAP_CHK_CFG,
  498. ssap=DpTelegram.SSAP_MS0):
  499. DpTelegram.__init__(self, da=da, sa=sa, fc=fc,
  500. dsap=dsap, ssap=ssap)
  501. self.clearCfgDataElements()
  502. def __repr__(self):
  503. return ("DpTelegram_ChkCfg_Req(da=%s, sa=%s, fc=%s, "
  504. "dsap=%s, ssap=%s cfgData=%s)" % (
  505. intToHex(self.da),
  506. intToHex(self.sa),
  507. intToHex(self.fc),
  508. intToHex(self.dsap),
  509. intToHex(self.ssap),
  510. str(self.cfgData)))
  511. def clearCfgDataElements(self):
  512. self.cfgData = []
  513. def addCfgDataElement(self, element):
  514. self.cfgData.append(element)
  515. @classmethod
  516. def fromFdlTelegram(cls, fdl):
  517. dp = cls(da=fdl.da,
  518. sa=fdl.sa,
  519. fc=fdl.fc,
  520. dsap=cls.extractSAP(fdl.dae),
  521. ssap=cls.extractSAP(fdl.sae))
  522. try:
  523. du = fdl.du
  524. while du:
  525. iden = du[0]
  526. idenType = iden & DpCfgDataElement.ID_TYPE_MASK
  527. if idenType == DpCfgDataElement.ID_TYPE_SPEC:
  528. nrBytes = iden & DpCfgDataElement.ID_LEN_MASK
  529. lengthBytes = du[1:1+nrBytes]
  530. if len(lengthBytes) != nrBytes:
  531. raise DpError("Invalid Config identifier")
  532. cfgData = DpCfgDataElement(identifier=iden,
  533. lengthBytes=lengthBytes)
  534. du = du[1+nrBytes:]
  535. else:
  536. cfgData = DpCfgDataElement(identifier=iden)
  537. du = du[1:]
  538. dp.addCfgDataElement(cfgData)
  539. except IndexError:
  540. raise DpError("Invalid Config telegram format")
  541. return dp
  542. def getDU(self):
  543. du = bytearray()
  544. for cfgData in self.cfgData:
  545. du.extend(cfgData.getDU())
  546. return du
  547. class _Cfg_Common(DpTelegram):
  548. __slots__ = (
  549. )
  550. def __init__(self, da, sa, fc, dsap, ssap):
  551. DpTelegram.__init__(self, da=da, sa=sa, fc=fc,
  552. dsap=dsap, ssap=ssap)
  553. def __repr__(self):
  554. return ("_Cfg_Common(da=%s, sa=%s, fc=%s, "
  555. "dsap=%s, ssap=%s)" % (
  556. intToHex(self.da),
  557. intToHex(self.sa),
  558. intToHex(self.fc),
  559. intToHex(self.dsap),
  560. intToHex(self.ssap)))
  561. class DpTelegram_GetCfg_Req(_Cfg_Common):
  562. __slots__ = (
  563. )
  564. def __init__(self, da, sa,
  565. fc=FdlTelegram.FC_SRD_HI |
  566. FdlTelegram.FC_REQ,
  567. dsap=DpTelegram.DSAP_GET_CFG,
  568. ssap=DpTelegram.SSAP_MS0):
  569. _Cfg_Common.__init__(self, da=da, sa=sa, fc=fc,
  570. dsap=dsap, ssap=ssap)
  571. @classmethod
  572. def fromFdlTelegram(cls, fdl):
  573. pass#TODO
  574. class DpTelegram_GetCfg_Con(_Cfg_Common):
  575. __slots__ = (
  576. )
  577. def __init__(self, da, sa,
  578. fc=FdlTelegram.FC_DL,
  579. dsap=DpTelegram.SSAP_MS0,
  580. ssap=DpTelegram.DSAP_GET_CFG):
  581. _Cfg_Common.__init__(self, da=da, sa=sa,
  582. fc=fc, dsap=dsap, ssap=ssap)
  583. @classmethod
  584. def fromFdlTelegram(cls, fdl):
  585. pass#TODO
  586. class DpTelegram_GlobalControl(DpTelegram):
  587. # Control_Command bits
  588. CCMD_CLEAR = 0x02 # Clear_Data: Clear all outputs
  589. CCMD_UNFREEZE = 0x04 # Unfreeze: Freezing is cancelled
  590. CCMD_FREEZE = 0x08 # Freeze: Inputs are frozen
  591. CCMD_UNSYNC = 0x10 # Unsync: Syncing is cancelled
  592. CCMD_SYNC = 0x20 # Sync: Outputs are synced
  593. # Group_Select values
  594. GSEL_BROADCAST = 0x00 # All slaves are addressed
  595. GSEL_GROUP1 = 0x01 # Group 1 is addressed
  596. GSEL_GROUP2 = 0x02 # Group 2 is addressed
  597. GSEL_GROUP3 = 0x04 # Group 3 is addressed
  598. GSEL_GROUP4 = 0x08 # Group 4 is addressed
  599. GSEL_GROUP5 = 0x10 # Group 5 is addressed
  600. GSEL_GROUP6 = 0x20 # Group 6 is addressed
  601. GSEL_GROUP7 = 0x40 # Group 7 is addressed
  602. GSEL_GROUP8 = 0x80 # Group 8 is addressed
  603. __slots__ = (
  604. "controlCommand",
  605. "groupSelect",
  606. )
  607. def __init__(self, da, sa,
  608. fc=FdlTelegram.FC_SDN_HI |
  609. FdlTelegram.FC_REQ,
  610. dsap=DpTelegram.DSAP_GLOBAL_CONTROL,
  611. ssap=DpTelegram.SSAP_MS0):
  612. DpTelegram.__init__(self, da=da, sa=sa, fc=fc,
  613. dsap=dsap, ssap=ssap)
  614. self.controlCommand = 0 # Control_Command
  615. self.groupSelect = self.GSEL_BROADCAST # Group_Select
  616. def __repr__(self):
  617. return ("DpTelegram_GlobalControl(da=%s, sa=%s, fc=%s, "
  618. "dsap=%s, ssap=%s)" % (
  619. intToHex(self.da),
  620. intToHex(self.sa),
  621. intToHex(self.fc),
  622. intToHex(self.dsap),
  623. intToHex(self.ssap)))
  624. @classmethod
  625. def fromFdlTelegram(cls, fdl):
  626. dp = cls(da=fdl.da,
  627. sa=fdl.sa,
  628. fc=fdl.fc,
  629. dsap=cls.extractSAP(fdl.dae),
  630. ssap=cls.extractSAP(fdl.sae))
  631. try:
  632. dp.controlCommand = fdl.du[0]
  633. dp.groupSelect = fdl.du[1]
  634. except IndexError:
  635. raise DpError("Invalid Global_Control telegram format")
  636. return dp
  637. def getDU(self):
  638. return bytearray((self.controlCommand,
  639. self.groupSelect))