cnccontrol_driver.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. #!/usr/bin/env python3
  2. """
  3. #
  4. # CNC-control - Host driver
  5. #
  6. # Copyright (C) 2011-2023 Michael Büsch <m@bues.ch>
  7. #
  8. """
  9. import sys
  10. import usb
  11. import errno
  12. import time
  13. from datetime import datetime, timedelta
  14. IDVENDOR = 0x6666
  15. IDPRODUCT = 0xC8CC
  16. EP_IN = 0x82
  17. EP_OUT = 0x02
  18. EP_IRQ = 0x81
  19. ALL_AXES = "xyzuvwabc"
  20. AXIS2NUMBER = dict((x[1], x[0]) for x in enumerate(ALL_AXES))
  21. NUMBER2AXIS = dict(enumerate(ALL_AXES))
  22. def equal(a, b, threshold=0.00001):
  23. if type(a) == float or type(b) == float:
  24. return abs(float(a) - float(b)) <= threshold
  25. return a == b
  26. def twos32(nr): # Convert 32bit two's complement value to python int.
  27. if nr & 0x80000000:
  28. return (-(~nr & 0xFFFFFFFF)) - 1
  29. return nr
  30. def crc8(crc, data):
  31. crc = crc ^ data
  32. for i in range(0, 8):
  33. if crc & 0x01:
  34. crc = (crc >> 1) ^ 0x8C
  35. else:
  36. crc >>= 1
  37. return crc & 0xFF
  38. def crc8Buf(crc, iterable):
  39. for b in iterable:
  40. crc = crc8(crc, b)
  41. return crc
  42. class CNCCException(Exception):
  43. @classmethod
  44. def info(cls, message):
  45. print("CNC-Control:", message)
  46. @classmethod
  47. def warn(cls, message):
  48. print("CNC-Control WARNING:", message)
  49. @classmethod
  50. def error(cls, message):
  51. raise cls(message)
  52. class CNCCFatal(CNCCException):
  53. pass
  54. class FixPt:
  55. FIXPT_FRAC_BITS = 16
  56. MIN_INT_LIMIT = -(1 << (FIXPT_FRAC_BITS - 1))
  57. MIN_LIMIT = float(MIN_INT_LIMIT) - 0.99999
  58. MAX_INT_LIMIT = (1 << (FIXPT_FRAC_BITS - 1)) - 1
  59. MAX_LIMIT = float(MAX_INT_LIMIT) + 0.99999
  60. def __init__(self, val):
  61. if isinstance(val, FixPt):
  62. self.raw = val.raw
  63. self.floatval = val.floatval
  64. return
  65. if type(val) == float:
  66. self.assertRepresentable(val)
  67. raw = (int(val * float(1 << self.FIXPT_FRAC_BITS)) + 1) & 0xFFFFFFFF
  68. self.raw = self.__int2raw(raw)
  69. self.floatval = val
  70. return
  71. if type(val) == int:
  72. self.assertRepresentable(val)
  73. self.raw = self.__int2raw(val << self.FIXPT_FRAC_BITS)
  74. self.floatval = float(val)
  75. return
  76. try: # Try 32bit LE two's complement
  77. self.raw = tuple(val)
  78. raw = twos32(val[0] | (val[1] << 8) |\
  79. (val[2] << 16) | (val[3] << 24))
  80. self.floatval = round(float(raw) / float(1 << self.FIXPT_FRAC_BITS), 4)
  81. except (TypeError, IndexError) as e:
  82. CNCCException.error("FixPt TypeError")
  83. @classmethod
  84. def representable(cls, val):
  85. # Returns true, if the integer part is representable by FixPt
  86. ival = int(val)
  87. return ival > cls.MIN_INT_LIMIT if ival < 0 else\
  88. ival < cls.MAX_INT_LIMIT
  89. @classmethod
  90. def assertRepresentable(cls, val):
  91. if cls.representable(val):
  92. return
  93. CNCCException.error("FixPt: Value %s is not representable" %\
  94. str(val))
  95. @staticmethod
  96. def __int2raw(val):
  97. return (val & 0xFF, (val >> 8) & 0xFF,
  98. (val >> 16) & 0xFF, (val >> 24) & 0xFF)
  99. def __repr__(self):
  100. return "0x%02X%02X%02X%02X -> %f" %\
  101. (self.raw[3], self.raw[2], self.raw[1], self.raw[0],
  102. self.floatval)
  103. def getRaw(self):
  104. return self.raw
  105. def __eq__(self, other):
  106. return self.raw == other.raw
  107. def __ne__(self, other):
  108. return self.raw != other.raw
  109. def isNegative(self):
  110. return bool(self.raw[3] & 0x80)
  111. class ControlMsg:
  112. # IDs
  113. CONTROL_PING = 0
  114. CONTROL_RESET = 1
  115. CONTROL_DEVFLAGS = 2
  116. CONTROL_AXISUPDATE = 3
  117. CONTROL_SPINDLEUPDATE = 4
  118. CONTROL_FOUPDATE = 5
  119. CONTROL_AXISENABLE = 6
  120. CONTROL_ESTOPUPDATE = 7
  121. CONTROL_SETINCREMENT = 8
  122. CONTROL_ENTERBOOT = 0xA0
  123. CONTROL_EXITBOOT = 0xA1
  124. CONTROL_BOOT_WRITEBUF = 0xA2
  125. CONTROL_BOOT_FLASHPG = 0xA3
  126. # Flags
  127. CONTROL_FLG_BOOTLOADER = 0x80
  128. # Targets
  129. TARGET_CPU = 0
  130. TARGET_COPROC = 1
  131. def __init__(self, id, flags, seqno):
  132. self.id = id
  133. self.flags = flags
  134. self.reserved = 0
  135. self.seqno = seqno
  136. def setSeqno(self, seqno):
  137. self.seqno = seqno
  138. def getRaw(self):
  139. return [self.id & 0xFF, self.flags & 0xFF,
  140. self.reserved & 0xFF, self.seqno & 0xFF]
  141. class ControlMsgPing(ControlMsg):
  142. def __init__(self, hdrFlags=0, hdrSeqno=0):
  143. ControlMsg.__init__(self, ControlMsg.CONTROL_PING,
  144. hdrFlags, hdrSeqno)
  145. class ControlMsgReset(ControlMsg):
  146. def __init__(self, hdrFlags=0, hdrSeqno=0):
  147. ControlMsg.__init__(self, ControlMsg.CONTROL_RESET,
  148. hdrFlags, hdrSeqno)
  149. class ControlMsgDevflags(ControlMsg):
  150. DEVICE_FLG_NODEBUG = (1 << 0)
  151. DEVICE_FLG_VERBOSEDBG = (1 << 1)
  152. DEVICE_FLG_ON = (1 << 2)
  153. DEVICE_FLG_TWOHANDEN = (1 << 3)
  154. DEVICE_FLG_USBLOGMSG = (1 << 4)
  155. DEVICE_FLG_G53COORDS = (1 << 5)
  156. def __init__(self, devFlagsMask, devFlagsSet, hdrFlags=0, hdrSeqno=0):
  157. ControlMsg.__init__(self, ControlMsg.CONTROL_DEVFLAGS,
  158. hdrFlags, hdrSeqno)
  159. self.devFlagsMask = devFlagsMask
  160. self.devFlagsSet = devFlagsSet
  161. def getRaw(self):
  162. raw = ControlMsg.getRaw(self)
  163. raw.extend( [self.devFlagsMask & 0xFF, (self.devFlagsMask >> 8) & 0xFF] )
  164. raw.extend( [self.devFlagsSet & 0xFF, (self.devFlagsSet >> 8) & 0xFF] )
  165. return raw
  166. class ControlMsgAxisupdate(ControlMsg):
  167. def __init__(self, pos, axis, hdrFlags=0, hdrSeqno=0):
  168. ControlMsg.__init__(self, ControlMsg.CONTROL_AXISUPDATE,
  169. hdrFlags, hdrSeqno)
  170. self.pos = FixPt(pos)
  171. self.axis = AXIS2NUMBER[axis]
  172. def getRaw(self):
  173. raw = ControlMsg.getRaw(self)
  174. raw.extend(self.pos.getRaw())
  175. raw.append(self.axis & 0xFF)
  176. return raw
  177. class ControlMsgSpindleupdate(ControlMsg):
  178. SPINDLE_OFF = 0
  179. SPINDLE_CW = 1
  180. SPINDLE_CCW = 2
  181. def __init__(self, state, hdrFlags=0, hdrSeqno=0):
  182. ControlMsg.__init__(self, ControlMsg.CONTROL_SPINDLEUPDATE,
  183. hdrFlags, hdrSeqno)
  184. self.state = state
  185. def getRaw(self):
  186. raw = ControlMsg.getRaw(self)
  187. raw.append(self.state & 0xFF)
  188. return raw
  189. class ControlMsgFoupdate(ControlMsg):
  190. def __init__(self, percent, hdrFlags=0, hdrSeqno=0):
  191. ControlMsg.__init__(self, ControlMsg.CONTROL_FOUPDATE,
  192. hdrFlags, hdrSeqno)
  193. self.percent = percent
  194. def getRaw(self):
  195. raw = ControlMsg.getRaw(self)
  196. raw.append(self.percent & 0xFF)
  197. return raw
  198. class ControlMsgAxisenable(ControlMsg):
  199. def __init__(self, mask, hdrFlags=0, hdrSeqno=0):
  200. ControlMsg.__init__(self, ControlMsg.CONTROL_AXISENABLE,
  201. hdrFlags, hdrSeqno)
  202. self.mask = mask
  203. def getRaw(self):
  204. raw = ControlMsg.getRaw(self)
  205. raw.extend( [self.mask & 0xFF, (self.mask >> 8) & 0xFF] )
  206. return raw
  207. class ControlMsgEstopupdate(ControlMsg):
  208. def __init__(self, asserted, hdrFlags=0, hdrSeqno=0):
  209. ControlMsg.__init__(self, ControlMsg.CONTROL_ESTOPUPDATE,
  210. hdrFlags, hdrSeqno)
  211. self.asserted = 1 if asserted else 0
  212. def getRaw(self):
  213. raw = ControlMsg.getRaw(self)
  214. raw.append(self.asserted & 0xFF)
  215. return raw
  216. class ControlMsgSetincrement(ControlMsg):
  217. MAX_INDEX = 5
  218. MAX_INC_FLOAT = 9.999
  219. def __init__(self, increment, index, hdrFlags=0, hdrSeqno=0):
  220. ControlMsg.__init__(self, ControlMsg.CONTROL_SETINCREMENT,
  221. hdrFlags, hdrSeqno)
  222. self.increment = FixPt(increment)
  223. self.index = index
  224. if self.increment.isNegative():
  225. CNCCFatal.error("Invalid negative JOG increment %f at index %d" %\
  226. (self.increment.floatval, index))
  227. if self.increment.floatval > ControlMsgSetincrement.MAX_INC_FLOAT:
  228. CNCCFatal.error("JOG increment %f at index %d is too big. Max = %f" %\
  229. (self.increment.floatval, index, ControlMsgSetincrement.MAX_INC_FLOAT))
  230. def getRaw(self):
  231. raw = ControlMsg.getRaw(self)
  232. raw.extend(self.increment.getRaw())
  233. raw.append(self.index & 0xFF)
  234. return raw
  235. class ControlMsgEnterboot(ControlMsg):
  236. ENTERBOOT_MAGIC0 = 0xB0
  237. ENTERBOOT_MAGIC1 = 0x07
  238. def __init__(self, target, hdrFlags=0, hdrSeqno=0):
  239. ControlMsg.__init__(self, ControlMsg.CONTROL_ENTERBOOT,
  240. hdrFlags, hdrSeqno)
  241. self.magic = (
  242. ControlMsgEnterboot.ENTERBOOT_MAGIC0,
  243. ControlMsgEnterboot.ENTERBOOT_MAGIC1,
  244. )
  245. self.target = target
  246. def getRaw(self):
  247. raw = ControlMsg.getRaw(self)
  248. raw.extend(self.magic)
  249. raw.append(self.target & 0xFF)
  250. return raw
  251. class ControlMsgExitboot(ControlMsg):
  252. def __init__(self, target,
  253. hdrFlags=ControlMsg.CONTROL_FLG_BOOTLOADER, hdrSeqno=0):
  254. ControlMsg.__init__(self, ControlMsg.CONTROL_EXITBOOT,
  255. hdrFlags, hdrSeqno)
  256. self.target = target
  257. def getRaw(self):
  258. raw = ControlMsg.getRaw(self)
  259. raw.append(self.target & 0xFF)
  260. return raw
  261. class ControlMsgBootWritebuf(ControlMsg):
  262. DATA_MAX_BYTES = 32
  263. def __init__(self, offset, data,
  264. hdrFlags=ControlMsg.CONTROL_FLG_BOOTLOADER, hdrSeqno=0):
  265. ControlMsg.__init__(self, ControlMsg.CONTROL_BOOT_WRITEBUF,
  266. hdrFlags, hdrSeqno)
  267. self.offset = offset
  268. self.size = len(data)
  269. self.crc = crc8Buf(0, data) ^ 0xFF
  270. nrPadding = ControlMsgBootWritebuf.DATA_MAX_BYTES - len(data)
  271. if nrPadding < 0:
  272. CNCCException.error("ControlMsg-BootWritebuf: invalid data length %d" %\
  273. (len(data)))
  274. self.data = data
  275. self.data.extend([0] * nrPadding)
  276. def getRaw(self):
  277. raw = ControlMsg.getRaw(self)
  278. raw.extend( [self.offset & 0xFF, (self.offset >> 8) & 0xFF] )
  279. raw.append(self.size & 0xFF)
  280. raw.append(self.crc & 0xFF)
  281. raw.extend(self.data)
  282. return raw
  283. class ControlMsgBootFlashpg(ControlMsg):
  284. def __init__(self, address, target,
  285. hdrFlags=ControlMsg.CONTROL_FLG_BOOTLOADER, hdrSeqno=0):
  286. ControlMsg.__init__(self, ControlMsg.CONTROL_BOOT_FLASHPG,
  287. hdrFlags, hdrSeqno)
  288. self.address = address
  289. self.target = target
  290. def getRaw(self):
  291. raw = ControlMsg.getRaw(self)
  292. raw.extend( [self.address & 0xFF, (self.address >> 8) & 0xFF] )
  293. raw.append(self.target & 0xFF)
  294. return raw
  295. class ControlReply:
  296. MAX_SIZE = 6
  297. # IDs
  298. REPLY_OK = 0
  299. REPLY_ERROR = 1
  300. REPLY_VAL16 = 2
  301. def __init__(self, id, flags, seqno):
  302. self.id = id
  303. self.flags = flags
  304. self.reserved = 0
  305. self.seqno = seqno
  306. @staticmethod
  307. def parseRaw(raw):
  308. try:
  309. id = raw[0]
  310. flags = raw[1]
  311. #reserved = raw[2]
  312. seqno = raw[3]
  313. raw = raw[4:]
  314. if id == ControlReply.REPLY_OK:
  315. return ControlReplyOk(hdrFlags=flags, hdrSeqno=seqno)
  316. elif id == ControlReply.REPLY_ERROR:
  317. return ControlReplyError(raw[0],
  318. hdrFlags=flags, hdrSeqno=seqno)
  319. elif id == ControlReply.REPLY_VAL16:
  320. return ControlReplyVal16(raw[0] | (raw[1] << 8),
  321. hdrFlags=flags, hdrSeqno=seqno)
  322. else:
  323. CNCCException.error("Unknown ControlReply ID: %d" % id)
  324. except (IndexError, KeyError):
  325. CNCCException.error("Failed to parse ControlReply (%d bytes)" % len(raw))
  326. def __repr__(self):
  327. return "Unknown reply code"
  328. def isOK(self):
  329. return False
  330. class ControlReplyOk(ControlReply):
  331. def __init__(self, hdrFlags=0, hdrSeqno=0):
  332. ControlReply.__init__(self, ControlReply.REPLY_OK,
  333. hdrFlags, hdrSeqno)
  334. def __repr__(self):
  335. return "Ok"
  336. def isOK(self):
  337. return True
  338. class ControlReplyError(ControlReply):
  339. # Error codes
  340. CTLERR_UNDEFINED = 0
  341. CTLERR_COMMAND = 1
  342. CTLERR_SIZE = 2
  343. CTLERR_BUSY = 3
  344. CTLERR_PERMISSION = 4
  345. CTLERR_INVAL = 5
  346. CTLERR_CONTEXT = 6
  347. CTLERR_CHECKSUM = 7
  348. CTLERR_CMDFAIL = 8
  349. def __init__(self, code, hdrFlags=0, hdrSeqno=0):
  350. ControlReply.__init__(self, ControlReply.REPLY_ERROR,
  351. hdrFlags, hdrSeqno)
  352. self.code = code
  353. def __repr__(self):
  354. code2text = {
  355. self.CTLERR_UNDEFINED : "Undefined error",
  356. self.CTLERR_COMMAND : "Command not implemented",
  357. self.CTLERR_SIZE : "Payload size error",
  358. self.CTLERR_BUSY : "Device is busy",
  359. self.CTLERR_PERMISSION : "Permission denied",
  360. self.CTLERR_INVAL : "Invalid parameters",
  361. self.CTLERR_CONTEXT : "Not supported in this context",
  362. self.CTLERR_CHECKSUM : "Data checksum error",
  363. self.CTLERR_CMDFAIL : "Command failed",
  364. }
  365. try:
  366. return code2text[self.code]
  367. except KeyError:
  368. return "Unknown error"
  369. class ControlReplyVal16(ControlReply):
  370. def __init__(self, value, hdrFlags=0, hdrSeqno=0):
  371. ControlReply.__init__(self, ControlReply.REPLY_VAL16,
  372. hdrFlags, hdrSeqno)
  373. self.value = value
  374. def __repr__(self):
  375. return "0x%04X" % self.value
  376. def isOK(self):
  377. return True
  378. class ControlIrq:
  379. MAX_SIZE = 14
  380. # IDs
  381. IRQ_JOG = 0
  382. IRQ_JOG_KEEPALIFE = 1
  383. IRQ_SPINDLE = 2
  384. IRQ_FEEDOVERRIDE = 3
  385. IRQ_DEVFLAGS = 4
  386. IRQ_HALT = 5
  387. IRQ_LOGMSG = 6
  388. # Flags
  389. IRQ_FLG_TXQOVR = (1 << 0)
  390. IRQ_FLG_PRIO = (1 << 1)
  391. IRQ_FLG_DROPPABLE = (1 << 2)
  392. def __init__(self, id, flags, seqno):
  393. self.id = id
  394. self.flags = flags
  395. self.reserved = 0
  396. self.seqno = seqno
  397. @staticmethod
  398. def parseRaw(raw):
  399. try:
  400. id = raw[0]
  401. flags = raw[1]
  402. #reserved = raw[2]
  403. seqno = raw[3]
  404. raw = raw[4:]
  405. if id == ControlIrq.IRQ_JOG:
  406. return ControlIrqJog(raw[0:4], raw[4:8], raw[8], raw[9],
  407. hdrFlags=flags, hdrSeqno=seqno)
  408. elif id == ControlIrq.IRQ_JOG_KEEPALIFE:
  409. return ControlIrqJogKeepalife(hdrFlags=flags, hdrSeqno=seqno)
  410. elif id == ControlIrq.IRQ_SPINDLE:
  411. return ControlIrqSpindle(raw[0],
  412. hdrFlags=flags, hdrSeqno=seqno)
  413. elif id == ControlIrq.IRQ_FEEDOVERRIDE:
  414. return ControlIrqFeedoverride(raw[0],
  415. hdrFlags=flags, hdrSeqno=seqno)
  416. elif id == ControlIrq.IRQ_DEVFLAGS:
  417. return ControlIrqDevflags(raw[0:2],
  418. hdrFlags=flags, hdrSeqno=seqno)
  419. elif id == ControlIrq.IRQ_HALT:
  420. return ControlIrqHalt(hdrFlags=flags, hdrSeqno=seqno)
  421. elif id == ControlIrq.IRQ_LOGMSG:
  422. return ControlIrqLogmsg(raw[0:10],
  423. hdrFlags=flags, hdrSeqno=seqno)
  424. else:
  425. CNCCException.error("Unknown ControlIrq ID: %d" % id)
  426. except (IndexError, KeyError):
  427. CNCCException.error("Failed to parse ControlIrq (%d bytes)" % len(raw))
  428. def __repr__(self):
  429. return "Unknown interrupt"
  430. class ControlIrqJog(ControlIrq):
  431. IRQ_JOG_CONTINUOUS = (1 << 0)
  432. IRQ_JOG_RAPID = (1 << 1)
  433. def __init__(self, increment, velocity, axis, flags,
  434. hdrFlags=0, hdrSeqno=0):
  435. ControlIrq.__init__(self, ControlIrq.IRQ_JOG,
  436. hdrFlags, hdrSeqno)
  437. self.increment = FixPt(increment)
  438. self.velocity = FixPt(velocity)
  439. self.axis = NUMBER2AXIS[axis]
  440. self.jogFlags = flags
  441. def __repr__(self):
  442. return "JOG interrupt (nr%d): %s, %s, %s, 0x%X" %\
  443. (self.seqno, str(self.increment),
  444. str(self.velocity), self.axis, self.jogFlags)
  445. class ControlIrqJogKeepalife(ControlIrq):
  446. def __init__(self, hdrFlags=0, hdrSeqno=0):
  447. ControlIrq.__init__(self, ControlIrq.IRQ_JOG_KEEPALIFE,
  448. hdrFlags, hdrSeqno)
  449. def __repr__(self):
  450. return "JOG KEEPALIFE interrupt (nr%d)" % (self.seqno)
  451. class ControlIrqSpindle(ControlIrq):
  452. def __init__(self, state, hdrFlags=0, hdrSeqno=0):
  453. ControlIrq.__init__(self, ControlIrq.IRQ_SPINDLE,
  454. hdrFlags, hdrSeqno)
  455. self.state = state
  456. def __repr__(self):
  457. return "SPINDLE interrupt (nr%d): %d" % (self.seqno, self.state)
  458. class ControlIrqFeedoverride(ControlIrq):
  459. def __init__(self, state, hdrFlags=0, hdrSeqno=0):
  460. ControlIrq.__init__(self, ControlIrq.IRQ_FEEDOVERRIDE,
  461. hdrFlags, hdrSeqno)
  462. self.state = state
  463. def __repr__(self):
  464. return "FEEDOVERRIDE interrupt (nr%d): %d" % (self.seqno, self.state)
  465. class ControlIrqDevflags(ControlIrq):
  466. def __init__(self, devFlags, hdrFlags=0, hdrSeqno=0):
  467. ControlIrq.__init__(self, ControlIrq.IRQ_DEVFLAGS,
  468. hdrFlags, hdrSeqno)
  469. self.devFlags = devFlags[0] | (devFlags[1] << 8)
  470. def __repr__(self):
  471. return "DEVFLAGS interrupt (nr%d): %04X" % (self.seqno, self.devFlags)
  472. class ControlIrqHalt(ControlIrq):
  473. def __init__(self, hdrFlags=0, hdrSeqno=0):
  474. ControlIrq.__init__(self, ControlIrq.IRQ_HALT,
  475. hdrFlags, hdrSeqno)
  476. def __repr__(self):
  477. return "HALT interrupt (nr%d)" % (self.seqno)
  478. class ControlIrqLogmsg(ControlIrq):
  479. def __init__(self, msg, hdrFlags=0, hdrSeqno=0):
  480. ControlIrq.__init__(self, ControlIrq.IRQ_LOGMSG,
  481. hdrFlags, hdrSeqno)
  482. self.msg = "".join([c if type(c) == str else chr(c) for c in msg])
  483. def __repr__(self):
  484. return "LOGMSG interrupt (nr%d)" % (self.seqno)
  485. class JogState:
  486. KEEPALIFE_TIMEOUT = 0.3
  487. STOPDATA = (FixPt(0.0), False, FixPt(0.0))
  488. def __init__(self):
  489. self.reset()
  490. def get(self):
  491. direction, incremental, velocity =\
  492. self.__direction, self.__incremental, self.__velocity
  493. if not equal(direction.floatval, 0.0):
  494. if datetime.now() > self.__timeout:
  495. CNCCException.warn("Jog keepalife timer expired")
  496. self.reset()
  497. return self.STOPDATA
  498. return (direction, incremental, velocity)
  499. def set(self, direction, incremental, velocity):
  500. self.__direction, self.__incremental, self.__velocity =\
  501. direction, incremental, velocity
  502. self.keepAlife()
  503. def reset(self):
  504. self.set(self.STOPDATA[0], self.STOPDATA[1], self.STOPDATA[2])
  505. def keepAlife(self):
  506. self.__timeout = datetime.now() + timedelta(seconds=self.KEEPALIFE_TIMEOUT)
  507. class CNCControl:
  508. def __init__(self, verbose=False):
  509. self.deviceAvailable = False
  510. self.verbose = verbose
  511. @staticmethod
  512. def __haveEndpoint(interface, epAddress):
  513. found = [ep for ep in interface.endpoints if ep.address == epAddress]
  514. return bool(found)
  515. def __epClearHalt(self, interface, epAddress):
  516. if self.__haveEndpoint(interface, epAddress):
  517. self.usbh.clearHalt(epAddress)
  518. def deviceRunsBootloader(self):
  519. # Check if we're in application code
  520. ping = ControlMsgPing(hdrFlags=0)
  521. reply = self.controlMsgSyncReply(ping)
  522. if reply.isOK():
  523. return False
  524. if reply.id == ControlReply.REPLY_ERROR and\
  525. reply.code != ControlReplyError.CTLERR_CONTEXT:
  526. CNCCException.error("Failed to ping the application: %s" % str(reply))
  527. # Check if we're in the bootloader code
  528. ping = ControlMsgPing(hdrFlags=ControlMsg.CONTROL_FLG_BOOTLOADER)
  529. reply = self.controlMsgSyncReply(ping)
  530. if reply.isOK():
  531. return True
  532. if reply.id == ControlReply.REPLY_ERROR and\
  533. reply.code != ControlReplyError.CTLERR_CONTEXT:
  534. CNCCException.error("Failed to ping the bootloader: %s" % str(reply))
  535. CNCCException.error("Unknown PING error")
  536. def probe(self):
  537. if self.deviceAvailable:
  538. return True
  539. self.__initializeData()
  540. try:
  541. self.usbdev = self.__findDevice(IDVENDOR, IDPRODUCT)
  542. if not self.usbdev:
  543. return False
  544. time.sleep(0.1)
  545. self.usbh = self.usbdev.open()
  546. self.usbh.reset()
  547. time.sleep(0.2)
  548. config = self.usbdev.configurations[0]
  549. interface = config.interfaces[0][0]
  550. self.usbh.setConfiguration(config)
  551. self.usbh.claimInterface(interface)
  552. self.usbh.setAltInterface(interface)
  553. self.__epClearHalt(interface, EP_IN)
  554. self.__epClearHalt(interface, EP_OUT)
  555. self.__epClearHalt(interface, EP_IRQ)
  556. except usb.USBError as e:
  557. self.__usbError(e, fatal=True, origin="init")
  558. self.__devicePlug()
  559. return True
  560. def deviceReset(self):
  561. self.__initializeData()
  562. reply = self.controlMsgSyncReply(ControlMsgReset())
  563. if not reply.isOK():
  564. CNCCException.error("Failed to reset the device state")
  565. reply = self.controlMsgSyncReply(ControlMsgDevflags(0, 0))
  566. if not reply.isOK():
  567. CNCCException.error("Failed to read device flags")
  568. self.__interpretDevFlags(reply.value)
  569. def deviceAppPing(self):
  570. return self.controlMsgSyncReply(ControlMsgPing()).isOK()
  571. def reconnect(self, timeoutSec=15):
  572. if not self.deviceAvailable:
  573. return False
  574. timeout = datetime.now() + timedelta(seconds=timeoutSec)
  575. self.__deviceUnplug()
  576. while timeout > datetime.now():
  577. try:
  578. if self.probe():
  579. return True
  580. except CNCCException as e:
  581. pass
  582. time.sleep(0.05)
  583. return False
  584. def __initializeData(self):
  585. self.messageSequenceNumber = 0
  586. self.deviceIsOn = False
  587. self.g53coords = False
  588. self.estop = False
  589. self.motionHaltRequest = False
  590. self.axisPositions = { }
  591. self.axisPosUpdatePending = { }
  592. self.lastAxisPosUpdate = { }
  593. self.jogStates = { }
  594. for ax in ALL_AXES:
  595. self.axisPositions[ax] = FixPt(0.0)
  596. self.axisPosUpdatePending[ax] = False
  597. self.lastAxisPosUpdate[ax] = datetime(1970, 1, 1)
  598. self.jogStates[ax] = JogState()
  599. self.foState = 0
  600. self.spindleCommand = 0
  601. self.spindleState = 0
  602. self.feedOverridePercent = 0
  603. self.logMsgBuf = ""
  604. def __interpretDevFlags(self, devFlags):
  605. if devFlags & ControlMsgDevflags.DEVICE_FLG_ON:
  606. if not self.deviceIsOn:
  607. CNCCException.info("turned ON")
  608. self.deviceIsOn = True
  609. else:
  610. if self.deviceIsOn:
  611. CNCCException.info("turned OFF")
  612. self.deviceIsOn = False
  613. self.g53coords = bool(devFlags & ControlMsgDevflags.DEVICE_FLG_G53COORDS)
  614. @staticmethod
  615. def __findDevice(idVendor, idProduct):
  616. for bus in usb.busses():
  617. for device in bus.devices:
  618. if device.idVendor == idVendor and\
  619. device.idProduct == idProduct:
  620. return device
  621. return None
  622. def __devicePlug(self):
  623. self.deviceAvailable = True
  624. if self.verbose:
  625. CNCCException.info("device connected")
  626. def __deviceUnplug(self):
  627. if self.deviceAvailable:
  628. self.deviceAvailable = False
  629. CNCCException.info("device disconnected")
  630. def __deviceUnplugException(self, message):
  631. self.__deviceUnplug()
  632. CNCCFatal.error(message)
  633. def __usbError(self, usbException, fatal=False, origin=None):
  634. if usbException.errno == errno.ENODEV or\
  635. str(usbException).lower().find("no such device") >= 0:
  636. self.__deviceUnplugException("Unplug exception")
  637. cls = CNCCFatal if fatal else CNCCException
  638. cls.error("USB error%s: %s" %(
  639. ((" (%s)" % origin) if origin else ""),
  640. str(usbException)))
  641. def eventWait(self, timeoutMs=30):
  642. if not self.deviceAvailable:
  643. self.__deviceUnplugException()
  644. try:
  645. data = self.usbh.interruptRead(EP_IRQ, ControlIrq.MAX_SIZE,
  646. timeoutMs)
  647. except usb.USBError as e:
  648. if not e.errno:
  649. return False # Timeout. No event.
  650. self.__usbError(e, origin="eventWait")
  651. if data:
  652. self.__handleInterrupt(data)
  653. return True
  654. def __handleInterrupt(self, rawData):
  655. irq = ControlIrq.parseRaw(rawData)
  656. if irq.flags & ControlIrq.IRQ_FLG_TXQOVR:
  657. CNCCException.warn("Interrupt queue overflow detected")
  658. if irq.id == ControlIrq.IRQ_JOG:
  659. cont = bool(irq.jogFlags & ControlIrqJog.IRQ_JOG_CONTINUOUS)
  660. velocity = irq.velocity
  661. if irq.jogFlags & ControlIrqJog.IRQ_JOG_RAPID:
  662. velocity = FixPt(-1.0)
  663. state = self.jogStates[irq.axis]
  664. state.set(direction = irq.increment,
  665. incremental = not cont,
  666. velocity = velocity)
  667. elif irq.id == ControlIrq.IRQ_JOG_KEEPALIFE:
  668. for jogState in self.jogStates.values():
  669. jogState.keepAlife()
  670. elif irq.id == ControlIrq.IRQ_SPINDLE:
  671. irq2direction = {
  672. ControlMsgSpindleupdate.SPINDLE_OFF: 0,
  673. ControlMsgSpindleupdate.SPINDLE_CW: 1,
  674. ControlMsgSpindleupdate.SPINDLE_CCW: -1,
  675. }
  676. self.spindleCommand = irq2direction[irq.state]
  677. elif irq.id == ControlIrq.IRQ_FEEDOVERRIDE:
  678. self.foState = irq.state
  679. elif irq.id == ControlIrq.IRQ_DEVFLAGS:
  680. self.__interpretDevFlags(irq.devFlags)
  681. elif irq.id == ControlIrq.IRQ_HALT:
  682. self.motionHaltRequest = True
  683. for jogState in self.jogStates.values():
  684. jogState.reset()
  685. self.spindleCommand = 0
  686. elif irq.id == ControlIrq.IRQ_LOGMSG:
  687. msg = self.logMsgBuf + irq.msg
  688. msg = msg.split('\n')
  689. while len(msg) > 1:
  690. print("[dev debug]:", msg[0])
  691. msg = msg[1:]
  692. self.logMsgBuf = msg[0]
  693. else:
  694. CNCCException.warn("Unhandled IRQ: " + str(irq))
  695. def controlMsg(self, msg, timeoutMs=300):
  696. try:
  697. msg.setSeqno(self.messageSequenceNumber)
  698. self.messageSequenceNumber = (self.messageSequenceNumber + 1) & 0xFF
  699. rawData = msg.getRaw()
  700. size = self.usbh.bulkWrite(EP_OUT, rawData, timeoutMs)
  701. if len(rawData) != size:
  702. CNCCException.error("Only wrote %d bytes of %d bytes "
  703. "bulk write" % (size, len(rawData)))
  704. except usb.USBError as e:
  705. self.__usbError(e, origin="controlMsg")
  706. def controlReply(self, timeoutMs=300):
  707. try:
  708. data = self.usbh.bulkRead(EP_IN, ControlReply.MAX_SIZE, timeoutMs)
  709. except usb.USBError as e:
  710. self.__usbError(e, origin="controlReply")
  711. return ControlReply.parseRaw(data)
  712. def controlMsgSyncReply(self, msg, timeoutMs=300):
  713. self.controlMsg(msg, timeoutMs)
  714. reply = self.controlReply(timeoutMs)
  715. if msg.seqno != reply.seqno:
  716. CNCCException.error("Got invalid reply sequence number: %d vs %d" %\
  717. (msg.seqno, reply.seqno))
  718. return reply
  719. def setTwohandEnabled(self, enable):
  720. if not self.deviceAvailable:
  721. self.__deviceUnplugException()
  722. flg = 0
  723. if enable:
  724. flg = ControlMsgDevflags.DEVICE_FLG_TWOHANDEN
  725. msg = ControlMsgDevflags(ControlMsgDevflags.DEVICE_FLG_TWOHANDEN, flg)
  726. reply = self.controlMsgSyncReply(msg)
  727. if not reply.isOK():
  728. CNCCException.error("Failed to set Twohand flag")
  729. def setIncrementAtIndex(self, index, increment):
  730. if not self.deviceAvailable:
  731. self.__deviceUnplugException()
  732. if equal(increment, 0.0):
  733. increment = FixPt(0)
  734. msg = ControlMsgSetincrement(increment, index)
  735. reply = self.controlMsgSyncReply(msg)
  736. if not reply.isOK():
  737. CNCCException.error("Failed to set increment %f at index %d: %s" %\
  738. (increment, index, str(reply)))
  739. def setDebugging(self, debugLevel, usbMessages):
  740. # 0 => disabled, 1 => enabled, 2 => verbose
  741. if not self.deviceAvailable:
  742. self.__deviceUnplugException()
  743. flg = ControlMsgDevflags.DEVICE_FLG_NODEBUG
  744. if debugLevel >= 1:
  745. flg &= ~ControlMsgDevflags.DEVICE_FLG_NODEBUG
  746. if debugLevel >= 2:
  747. flg |= ControlMsgDevflags.DEVICE_FLG_VERBOSEDBG
  748. if usbMessages:
  749. flg |= ControlMsgDevflags.DEVICE_FLG_USBLOGMSG
  750. msg = ControlMsgDevflags(ControlMsgDevflags.DEVICE_FLG_NODEBUG |
  751. ControlMsgDevflags.DEVICE_FLG_VERBOSEDBG |
  752. ControlMsgDevflags.DEVICE_FLG_USBLOGMSG,
  753. flg)
  754. reply = self.controlMsgSyncReply(msg)
  755. if not reply.isOK():
  756. CNCCException.error("Failed to set debugging flags")
  757. def setEstopState(self, asserted):
  758. # Send the estop state to the device
  759. if not self.deviceAvailable:
  760. self.__deviceUnplugException()
  761. if asserted == self.estop:
  762. return # No change
  763. msg = ControlMsgEstopupdate(asserted)
  764. reply = self.controlMsgSyncReply(msg)
  765. if not reply.isOK():
  766. CNCCException.error("Failed to send ESTOP update")
  767. self.estop = asserted
  768. def deviceIsTurnedOn(self):
  769. if not self.deviceAvailable:
  770. self.__deviceUnplugException()
  771. return self.deviceIsOn
  772. def haveMotionHaltRequest(self):
  773. if not self.deviceAvailable:
  774. self.__deviceUnplugException()
  775. halt = self.motionHaltRequest
  776. self.motionHaltRequest = False
  777. return halt
  778. def getSpindleCommand(self):
  779. # Returns -1, 0 or 1 for reverse, stop or forward.
  780. if not self.deviceAvailable:
  781. self.__deviceUnplugException()
  782. return self.spindleCommand
  783. def setSpindleState(self, direction):
  784. if not self.deviceAvailable:
  785. self.__deviceUnplugException()
  786. if self.spindleState == direction:
  787. return
  788. self.spindleState = direction
  789. direction2state = {
  790. 0: ControlMsgSpindleupdate.SPINDLE_OFF,
  791. 1: ControlMsgSpindleupdate.SPINDLE_CW,
  792. -1: ControlMsgSpindleupdate.SPINDLE_CCW,
  793. }
  794. msg = ControlMsgSpindleupdate(direction2state[direction])
  795. reply = self.controlMsgSyncReply(msg)
  796. if not reply.isOK():
  797. CNCCException.error("Failed to send spindle update")
  798. def getFeedOverrideState(self, minValue, maxValue):
  799. # Returns override state in percent (float)
  800. if not self.deviceAvailable:
  801. self.__deviceUnplugException()
  802. inRange = 256
  803. outRange = maxValue - minValue
  804. mult = outRange / inRange
  805. return self.foState * mult + minValue
  806. def setFeedOverrideState(self, percent):
  807. # Sends the current FO percentage state to the device
  808. if not self.deviceAvailable:
  809. self.__deviceUnplugException()
  810. if self.feedOverridePercent == percent:
  811. return # No change
  812. msg = ControlMsgFoupdate(int(round(percent)))
  813. reply = self.controlMsgSyncReply(msg)
  814. if not reply.isOK():
  815. CNCCException.error("Failed to send feed override state")
  816. self.feedOverridePercent = percent
  817. def setAxisPosition(self, axis, position):
  818. # Update axis position on device
  819. if not self.deviceAvailable:
  820. self.__deviceUnplugException()
  821. pos = FixPt(position)
  822. if pos != self.axisPositions[axis]:
  823. self.axisPositions[axis] = pos
  824. self.axisPosUpdatePending[axis] = True
  825. if self.axisPosUpdatePending[axis]:
  826. now = datetime.now()
  827. if now < self.lastAxisPosUpdate[axis] + timedelta(seconds=0.1):
  828. return # Not yet
  829. msg = ControlMsgAxisupdate(pos, axis)
  830. reply = self.controlMsgSyncReply(msg)
  831. if not reply.isOK():
  832. CNCCException.error("Axis update failed: %s" % str(reply))
  833. self.axisPosUpdatePending[axis] = False
  834. self.lastAxisPosUpdate[axis] = now
  835. def wantG53Coords(self):
  836. return self.g53coords
  837. def setEnabledAxes(self, axes):
  838. # Set the enabled axes.
  839. if not self.deviceAvailable:
  840. self.__deviceUnplugException()
  841. mask = 0
  842. for ax in axes:
  843. mask |= (1 << AXIS2NUMBER[ax])
  844. msg = ControlMsgAxisenable(mask)
  845. reply = self.controlMsgSyncReply(msg)
  846. if not reply.isOK():
  847. CNCCException.error("Failed to set axis mask: %s" % str(reply))
  848. def getJogState(self, axis):
  849. # Returns (direction, incremental, velocity)
  850. if not self.deviceAvailable:
  851. self.__deviceUnplugException()
  852. state = self.jogStates[axis]
  853. (direction, incremental, velocity) = state.get()
  854. retval = (direction.floatval,
  855. incremental,
  856. velocity.floatval)
  857. if incremental:
  858. state.reset()
  859. return retval