PageRenderTime 112ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/cl2.py

https://bitbucket.org/bewest/insulaudit
Python | 552 lines | 515 code | 5 blank | 32 comment | 0 complexity | dce9afb53bf0b973ad73e4ff3cb9c068 MD5 | raw file
  1. #!/usr/bin/python
  2. import user
  3. import struct
  4. import sys
  5. import serial
  6. import time
  7. import logging
  8. from pprint import pprint, pformat
  9. import doctest
  10. from insulaudit.core import Command
  11. from insulaudit.clmm.usbstick import *
  12. from insulaudit import lib
  13. logging.basicConfig( stream=sys.stdout )
  14. log = logging.getLogger( 'auditor' )
  15. log.setLevel( logging.DEBUG )
  16. log.info( 'hello world' )
  17. io = logging.getLogger( 'auditor.io' )
  18. io.setLevel( logging.DEBUG )
  19. """
  20. ######################
  21. #
  22. # ComLink2
  23. # pseudocode analysis of critical procedures
  24. # there is some implicit OO going on
  25. #
  26. execute(command):
  27. usbcommand.execute(self)
  28. ############################
  29. #
  30. # USB(Pump) Command Stuff
  31. #
  32. packSerialNumber:
  33. return makePackedBCD(serial)
  34. """
  35. """
  36. ######################
  37. #
  38. # Pump
  39. #
  40. # every command needs:
  41. # code, retries, params, length, pages
  42. initDevice:
  43. # cmdPowerControl Command(93, "rf power on", 2)
  44. # cmdPowerControl.params = [ 1, 1 ]
  45. # cmdPowerControl.retries = 0
  46. # cmdReadErrorStatus = Command(117, "read pump error status")
  47. # cmdReadState = Command(131, "Read Pump State")
  48. # cmdReadTempBasal = Command(152, "Read Temporary Basal")
  49. initDevice2
  50. iniDevice2:
  51. detectActiveBolus = Command(76, "set temp basal rate (bolus detection only)", 3)
  52. detectActiveBolus.params = [ 0, 0, 0 ]
  53. detectActiveBolus.retries = 0
  54. detectActiveBolus:
  55. # cmdDetectBolus
  56. shutDownPump
  57. if suspended:
  58. shutDownPump2()
  59. cmdCancelSuspend()
  60. # turn rf power off
  61. # retries 0
  62. cmdOff = Command(93, "rf power off", [ 0 ], 2)
  63. cmdOff.execute
  64. shutDownPump2:
  65. Command(91, "keypad push (ack)", [ 2 ], 1).execute
  66. time.sleep(.500)
  67. Command(91, "keypad push (esc)", [ 1 ], 1).execute
  68. time.sleep(.500)
  69. getNAKDescription:
  70. # pass
  71. # 2 params
  72. Command(code, descr)
  73. # 5: code, descr, bytesPerRecord, maxRecords, maxRetries
  74. return Command(code, descr, 64, 1, 0)
  75. # 3 params
  76. Command(code, descr, paramCount):
  77. # 5
  78. #
  79. com = Command(code, descr, 0, 1, 11)
  80. com.paramCount = paramCount
  81. numblocks = paramCount / 64 + 1
  82. # 4 params
  83. Command(code, descr, params, tail)
  84. # 5
  85. com = Command(code, descr, 0, 1, 11)
  86. com.params = params
  87. #com.paramCount
  88. # 5 params
  89. Command(code, descr, bytesPerRecord, maxRecords, ??):
  90. # likely decompile error
  91. # 7
  92. Command(code, descr, bytesPerRecord, maxRecords, 0, 0, paramCount)
  93. dataOffset = 0
  94. cmdLength = 2
  95. # 7 params
  96. Command(code, descr, bytesPerRecord, maxRecords, address, addressLength, arg8):
  97. offset = 2
  98. if addressLength == 1:
  99. cmdLength = 2 + addressLength
  100. else:
  101. cmdLength = 2 + addressLength + 1
  102. retries = 2
  103. # 511
  104. execute:
  105. result = None
  106. for i in xrange(maxRetries)
  107. # reset bytes read
  108. response = usb.execute(self)
  109. # handle stack trace
  110. if response: break
  111. return result
  112. """
  113. """
  114. """
  115. class Link( core.CommBuffer ):
  116. class ID:
  117. VENDOR = 0x0a21
  118. PRODUCT = 0x8001
  119. timeout = .100
  120. def __init__( self, port, timeout=None ):
  121. super(type(self), self).__init__(port, timeout)
  122. def setTimeout(self, timeout):
  123. self.serial.setTimeout(timeout)
  124. def getTimeout(self):
  125. return self.serial.getTimeout()
  126. def initUSBComms(self):
  127. self.initCommunicationsIO()
  128. #self.initDevice()
  129. def getSignalStrength(self):
  130. result = self.readSignalStrength()
  131. signal = result[0]
  132. def readSignalStrength(self):
  133. result = self.sendComLink2Command(6, 0)
  134. # result[0] is signal strength
  135. log.info('%r:readSignalStrength:%s' % (self, int(result[0])))
  136. return result
  137. def initCommunicationsIO(self):
  138. # close/open serial
  139. self.readProductInfo( )
  140. self.readSignalStrength()
  141. def endCommunicationsIO(self):
  142. self.readSignalStrength()
  143. self.readInterfaceStatistics()
  144. # close port
  145. self.close()
  146. def readProductInfo(self):
  147. result = self.sendComLink2Command(4)
  148. # 1/0/255
  149. log.info('readProductInfo:result')
  150. freq = result[5]
  151. info = self.decodeProductInfo(result)
  152. log.info('product info: %s' % pformat(info))
  153. # decodeInterface stats
  154. def decodeProductInfo(self, data):
  155. class F:
  156. body = data
  157. comm = USBProductInfo()
  158. comm.reply = F()
  159. comm.onACK()
  160. return comm.info
  161. def sendComLink2Command(self, msg, a2=0x00, a3=0x00):
  162. # generally commands are 3 bytes, most often CMD, 0x00, 0x00
  163. msg = bytearray([ msg, a2, a3 ])
  164. io.info('sendComLink2Command:write')
  165. self.write(msg)
  166. return self.checkAck()
  167. # throw local usb exception
  168. def checkAck(self):
  169. time.sleep(.100)
  170. result = bytearray(self.read(64))
  171. io.info('checkAck:read')
  172. commStatus = result[0]
  173. # usable response
  174. assert commStatus == 1
  175. status = result[1]
  176. # status == 102 'f' NAK, look up NAK
  177. if status == 85: # 'U'
  178. log.info('ACK OK')
  179. return result[3:]
  180. assert False, "NAK!!"
  181. def decodeIFaceStats(self, data):
  182. class F:
  183. body = data
  184. comm = InterfaceStats()
  185. comm.reply = F()
  186. comm.onACK()
  187. return comm.info
  188. def readInterfaceStatistics(self):
  189. # decode and log stats
  190. result = self.sendComLink2Command(5, 0)
  191. info = self.decodeIFaceStats(result)
  192. log.info("read radio Interface Stats: %s" % pformat(info))
  193. result = self.sendComLink2Command(5, 1)
  194. info = self.decodeIFaceStats(result)
  195. log.info("read stick Interface Stats: %s" % pformat(info))
  196. #######################
  197. #
  198. #
  199. #
  200. def CRC8(data):
  201. return lib.CRC8.compute(data)
  202. ################################
  203. # Remote Stuff
  204. #
  205. class BaseCommand(object):
  206. code = 0x00
  207. descr = "(error)"
  208. retries = 2
  209. timeout = 3
  210. params = [ ]
  211. bytesPerRecord = 0
  212. maxRecords = 0
  213. effectTime = 1
  214. def __init__(self, code, descr, *args):
  215. self.code = code
  216. self.descr = descr
  217. self.params = [ ]
  218. def format(self):
  219. pass
  220. def allocateRawData(self):
  221. self.raw = self.bytesPerRecord * self.maxRecords
  222. class Device(object):
  223. def __init__(self, link):
  224. self.link = link
  225. def execute(self, command):
  226. self.command = command
  227. self.allocateRawData()
  228. self.sendAndRead()
  229. def sendAndRead(self):
  230. self.sendDeviceCommand()
  231. time.sleep(self.command.effectTime)
  232. if self.expectedLength > 0:
  233. # in original code, this modifies the length tested in the previous if
  234. # statement
  235. self.command.data = self.readDeviceData()
  236. def sendDeviceCommand(self):
  237. packet = self.buildTransmitPacket()
  238. io.info('sendDeviceCommand:write:%r' % (self.command))
  239. self.link.write(packet)
  240. time.sleep(.500)
  241. code = self.command.code
  242. params = self.command.params
  243. if code != 93 or params[0] != 0:
  244. self.link.checkAck()
  245. def allocateRawData(self):
  246. self.command.allocateRawData()
  247. self.expectedLength = self.command.bytesPerRecord * self.command.maxRecords
  248. def readDeviceData(self):
  249. self.eod = False
  250. results = bytearray( )
  251. while not self.eod:
  252. data = self.readDeviceDataIO( )
  253. results.extend(data)
  254. return results
  255. def readDeviceDataIO(self):
  256. results = self.readData()
  257. lb, hb = results[5] & 0x7F, results[6]
  258. self.eod = (results[5] & 0x80) > 0
  259. resLength = lib.BangInt((lb, hb))
  260. assert resLength > 63, ("cmd low byte count:\n%s" % lib.hexdump(results))
  261. data = results[13:13+resLength]
  262. assert len(data) == resLength
  263. crc = results[-1]
  264. # crc check
  265. log.info('readDeviceDataIO:msgCRC:%r:expectedCRC:%r:data:%r' % (crc, CRC8(data), data))
  266. assert crc == CRC8(data)
  267. return data
  268. def readData(self):
  269. bytesAvailable = self.getNumBytesAvailable()
  270. packet = [12, 0, lib.HighByte(bytesAvailable), lib.LowByte(bytesAvailable)]
  271. packet.append( CRC8(packet) )
  272. response = self.writeAndRead(packet, bytesAvailable)
  273. # assert response.length > 14
  274. assert (int(response[0]) == 2), repr(response)
  275. # response[1] != 0 # interface number !=0
  276. # response[2] == 5 # timeout occurred
  277. # response[2] == 2 # NAK
  278. # response[2] # should be within 0..4
  279. log.info("readData ACK")
  280. return response
  281. def writeAndRead(self, msg, length):
  282. io.info("writeAndRead:")
  283. self.link.write(bytearray(msg))
  284. time.sleep(.300)
  285. self.link.setTimeout(self.command.timeout)
  286. return bytearray(self.link.read(length))
  287. def getNumBytesAvailable(self):
  288. result = self.readStatus( )
  289. start = time.time()
  290. i = 0
  291. while result == 0 and time.time() - start < 1:
  292. log.debug('%r:getNumBytesAvailable:attempt:%s' % (self, i))
  293. result = self.readStatus( )
  294. time.sleep(.100)
  295. i += 1
  296. log.info('getNumBytesAvailable:%s' % result)
  297. return result
  298. def readStatus(self):
  299. result = self.link.sendComLink2Command(3)
  300. commStatus = result[0] # 0 indicates success
  301. assert commStatus == 0
  302. status = result[2]
  303. lb, hb = result[3], result[4]
  304. bytesAvailable = lib.BangInt((lb, hb))
  305. self.status = status
  306. if (status & 0x1) > 0:
  307. return bytesAvailable
  308. return 0
  309. def buildTransmitPacket(self):
  310. return self.command.format( )
  311. class PumpCommand(BaseCommand):
  312. serial = '665455'
  313. #serial = '206525'
  314. params = [ ]
  315. bytesPerRecord = 64
  316. maxRecords = 1
  317. retries = 2
  318. __fields__ = ['maxRecords', 'code', 'descr',
  319. 'serial', 'bytesPerRecord', 'params']
  320. def __init__(self, **kwds):
  321. for k in self.__fields__:
  322. value = kwds.get(k, getattr(self, k))
  323. setattr(self, k, value)
  324. def getData(self):
  325. return self.data
  326. def format(self):
  327. params = self.params
  328. code = self.code
  329. maxRetries = self.retries
  330. serial = list(bytearray(self.serial.decode('hex')))
  331. paramsCount = len(params)
  332. head = [ 1, 0, 167, 1 ]
  333. # serial
  334. packet = head + serial
  335. # paramCount 2 bytes
  336. packet.extend( [ (0x80 | lib.HighByte(paramsCount)),
  337. lib.LowByte(paramsCount) ] )
  338. # not sure what this byte means
  339. button = 0
  340. # special case command 93
  341. if code == 93:
  342. button = 85
  343. packet.append(button)
  344. packet.append(maxRetries)
  345. # how many packets/frames/pages/flows will this take?
  346. responseSize = self.calcRecordsRequired()
  347. # really only 1 or 2?
  348. pages = responseSize
  349. if responseSize > 1:
  350. pages = 2
  351. packet.append(pages)
  352. packet.append(0)
  353. # command code goes here
  354. packet.append(code)
  355. packet.append(CRC8(packet))
  356. packet.extend(params)
  357. packet.append(CRC8(params))
  358. io.info(packet)
  359. return bytearray(packet)
  360. def calcRecordsRequired(self):
  361. length = self.bytesPerRecord * self.maxRecords
  362. i = length / 64
  363. j = length % 64
  364. if j > 0:
  365. return i + 1
  366. return i
  367. class PowerControl(PumpCommand):
  368. """
  369. >>> PowerControl().format() == PowerControl._test_ok
  370. True
  371. """
  372. _test_ok = bytearray( [ 0x01, 0x00, 0xA7, 0x01, 0x66, 0x54, 0x55, 0x80,
  373. 0x02, 0x55, 0x00, 0x00, 0x00, 0x5D, 0xE6, 0x01,
  374. 0x0A, 0xA2 ] )
  375. code = 93
  376. descr = "RF Power On"
  377. params = [ 0x01, 0x0A ]
  378. retries = 0
  379. maxRecords = 0
  380. timeout = 17
  381. effectTime = 17
  382. class PowerControlOff(PowerControl):
  383. params = [ 0x00, 0x0A ]
  384. class ReadErrorStatus(PumpCommand):
  385. """
  386. >>> ReadErrorStatus().format() == ReadErrorStatus._test_ok
  387. True
  388. """
  389. _test_ok = bytearray([ 0x01, 0x00, 0xA7, 0x01, 0x66, 0x54, 0x55, 0x80,
  390. 0x00, 0x00, 0x02, 0x01, 0x00, 0x75, 0xD7, 0x00 ])
  391. code = 117
  392. descr = "Read Error Status any current alarms set?"
  393. params = [ ]
  394. retries = 2
  395. maxRecords = 1
  396. class ReadPumpState(PumpCommand):
  397. """
  398. >>> ReadPumpState().format() == ReadPumpState._test_ok
  399. True
  400. """
  401. _test_ok = bytearray([ 0x01, 0x00, 0xA7, 0x01, 0x66, 0x54, 0x55, 0x80,
  402. 0x00, 0x00, 0x02, 0x01, 0x00, 0x83, 0x2E, 0x00 ])
  403. code = 131
  404. descr = "Read Pump State"
  405. params = [ ]
  406. retries = 2
  407. maxRecords = 1
  408. class ReadPumpModel(PumpCommand):
  409. """
  410. >>> ReadPumpModel().format() == ReadPumpModel._test_ok
  411. True
  412. """
  413. code = 141
  414. descr = "Read Pump Model Number"
  415. params = [ ]
  416. retries = 2
  417. maxRecords = 1
  418. _test_ok = bytearray([ 0x01, 0x00, 0xA7, 0x01, 0x66, 0x54, 0x55, 0x80,
  419. 0x00, 0x00, 0x02, 0x01, 0x00, 0x8D, 0x5B, 0x00 ])
  420. def getData(self):
  421. data = self.data
  422. length = data[0]
  423. msg = data[1:1+length]
  424. self.model = msg
  425. return str(msg)
  426. def initDevice(link):
  427. device = Device(link)
  428. comm = PowerControl()
  429. device.execute(comm)
  430. log.info('comm:%s:data:%s' % (comm, getattr(comm, 'data', None)))
  431. comm = ReadErrorStatus()
  432. device.execute(comm)
  433. log.info('comm:%s:data:%s' % (comm, getattr(comm, 'data', None)))
  434. comm = ReadPumpState()
  435. device.execute(comm)
  436. log.info('comm:%s:data:%s' % (comm, getattr(comm, 'data', None)))
  437. return device
  438. def do_commands(device):
  439. comm = ReadPumpModel( )
  440. device.execute(comm)
  441. log.info('comm:%s:data:%s' % (comm, getattr(comm.getData( ), 'data', None)))
  442. log.info('REMOTE PUMP MODEL NUMBER: %s' % comm.getData( ))
  443. def shutdownDevice(device):
  444. comm = PowerControlOff()
  445. device.execute(comm)
  446. log.info('comm:%s:data:%s' % (comm, getattr(comm, 'data', None)))
  447. if __name__ == '__main__':
  448. io.info("hello world")
  449. doctest.testmod( )
  450. port = None
  451. try:
  452. port = sys.argv[1]
  453. except IndexError, e:
  454. print "usage:\n%s /dev/ttyUSB0" % sys.argv[0]
  455. sys.exit(1)
  456. link = Link(port)
  457. link.initUSBComms()
  458. device = initDevice(link)
  459. do_commands(device)
  460. #shutdownDevice(device)
  461. link.endCommunicationsIO()
  462. #pprint( carelink( USBProductInfo( ) ).info )
  463. #####
  464. # EOF