def parse_03(byteList, msgDict): # Command to pod to finish initialization of address, TID, Lot """ My example: # 0x03 setup pod from Loop: msg = '03131f09f876140404011401290000b0c100077c0e' O 1 2 3 4 5 6 7 8 910 1112 [13141516 17181920] 03 LL IIIIIIII NU TO MMDDYY HHMM [LLLLLLLL TTTTTTTT] 03 (1 byte) [0]: mtype value of 03 specifies Setup Pod command LL (1 byte) [1]: mlen $13 for typical case or $0b IIIIIIII (4 bytes) [2:5]: Pod ID as given in the 07 Command NU (1 byte) [6]: Not Used TO (1 byte) [7]: Pkt Timeout Limit (max 50, 0 sets the default value of 4) MMDDYY (3 bytes) [8:$A]: MM: month (1-12), DD: day (1-31), YY: years since 2000 HHMM (2 bytes) [$B:$C]: HH: hour (0-23), MM minutes (0-59) LLLLLLLL (4 bytes) [$D:$10]: Lot (long format only) TTTTTTTT (4 bytes) [$11:$14]: TID (long format only) """ msgDict['msgMeaning'] = 'setupPod' if byteList[1] != 0x13: print('Unexpected length of 0x03 command for Loop') return msgDict podAddr = combineByte(byteList[2:6]) timeOut = byteList[7] MM = byteList[8] DD = byteList[9] YY = byteList[10] hh = byteList[11] mm = byteList[12] podLot = combineByte(byteList[13:17]) podTid = combineByte(byteList[17:21]) msgDict['podAddr'] = hex(podAddr) msgDict['timeOut'] = timeOut msgDict['dateStamp'] = '{0:#0{1}d}'.format(MM, 2) + '/' + \ '{0:#0{1}d}'.format(DD, 2) + '/' + \ '20{0:#0{1}d}'.format(YY, 2) + ' ' + \ '{0:#0{1}d}'.format(hh, 2) + ':' + \ '{0:#0{1}d}'.format(mm, 2) msgDict['lot'] = podLot msgDict['tid'] = podTid return msgDict
def unparsedMsg(byteList, msgDict): # 0x07 is so simple, just parse it here if byteList[0] == 0x07: msgDict['msgMeaning'] = 'assignID' msgDict['useAddr'] = hex(combineByte(byteList[2:6])) else: msgDict['msgMeaning'] = 'checkWiki' return msgDict
def parse_06(byteList, msgDict): # pod response - indicates a nonce resync is required """ My example: msg = '060314217881e4' 06 03 EE WWWW checksum 06 03 14 2178 81e4 A Pod 06 response typically indicates the PDM sent a bad nonce (PDM intentionally sends a fake nonce at each time it awakes). The PDM and Pod will both start a new nonce sequence to get back in sync and the PDM will try the request again with a new nonce. 06 03 EE WWWW 06 (1 byte): mtype of this response is 06 03 (1 byte): mlen for this response is always 03 EE (1 byte): error code, typically $14 for bad nonce; other values indicate a fault event WWWW (2 bytes): interpretation depends on EE value if EE == $14: encoded value indicating offset for new nonce sequence if EE != $14: logged event byte followed by a Pod progress byte (0..$F) For EE == $14 case, WWWW is a word value encoded with formula: (LSB Word of FakeNonce + crc16_table[Message sequence number] + (LSB word)Lot + (LSB word)TID) ^ NewSeed NewSeed can be extracted knowing the other parameters (Lot, TID, MessageSeq, FakeNonce) """ errorCode = byteList[2] wordCode = combineByte(byteList[3:5]) msgDict['is_nonce_resync'] = errorCode == 0x14 if msgDict['is_nonce_resync']: msgDict['msgType'] = '0x0614' msgDict['nonce_reseed_word'] = wordCode msgDict['fault_code'] = 'nonceResync' msgDict['msgMeaning'] = 'ResyncNonceOK' else: msgDict['nonce_reseed_word'] = 0 msgDict['fault_code'] = hex(errorCode) msgDict['msgMeaning'] = 'ResyncNonceNotOK' return msgDict
def parse_1c(byteList, msgDict): """ Example: msg = '1c041793f587' 1c 04 NNNNNNNN 1c 04 1793f587 The $1C Deactivate Pod command is used to fully deactivate the current Pod. The deactivate Pod command has no arguments other than the nonce. 1c (1 byte): mtype of this response is 06 04 (1 byte): mlen for this response is always 04 NN (4 byte): nonce """ msgDict['msgMeaning'] = 'DeactivatePod' msgDict['nonce'] = hex(combineByte(byteList[2:])) return msgDict
def splitFullMsg(hexToParse): """ splitFullMsg splits the hexToParse from Loop report into components See: https://github.com/openaps/openomni/wiki/Message-Structure An ACK hexToParse has 2 historical formats, both with empty message ## MessageLog <= 10 bytes with no CRC ## Device Communication has address, packetNumber and CRC Because all messages (except ACK) have seqNum, reuse seqNum key for the ACK packet number in msgDict """ address = hexToParse[:8] thisLen = len(hexToParse) # Handle older ## message log format for ACK if thisLen <= 10: byte89 = 0 # processMsg below returns ACK from an empty msg_body msg_body = '' CRC = '0000' # indicates no CRC provided else: byte89 = combineByte(list(bytearray.fromhex(hexToParse[8:10]))) msg_body = hexToParse[12:-4] CRC = hexToParse[-4:] msgDict = processMsg(msg_body) # for ACK, extract packet number (if available) - request from Joe # use seqNum key for storage if msgDict['msgType'] == 'ACK': packetNumber = (byte89 & 0x1F) msgDict['seqNum'] = packetNumber else: msgDict['seqNum'] = (byte89 & 0x3C) >> 2 msgDict['rawHex'] = hexToParse msgDict['CRC'] = '0x' + CRC if msgDict['msgType'] == '0x0202': print(f' ** {msgDict["msgMeaning"]}, gain: {msgDict["recvGain"]}, \ rssi: {msgDict["rssiValue"]}') return address, msgDict
def parse_08(byteList, msgDict): """ Command 08 is a special command that can be used at Pod startup to configure a couple of internal delivery flags. This command is has not been seen to be used by the PDM. The 08 Configure Delivery Flags command has the following format: OFF 1 2 3 4 5 6 7 08 06 NNNNNNNN JJ KK 08 (1 byte) [0]: mtype value of 08 is the Configure Delivery Flags command 06 (1 byte) [1]: mlen for this command is always 06 NNNNNNNN (4 bytes) [2:5]: the Nonce, 32-bit validator JJ (1 byte) [6]: new Tab5[0x16] value, 0 or 1; Pod default value is 1 KK (1 byte) [7]: new Tab5[0x17] value, 0 or 1; Pod default value is 0 The JJ byte should be set to 00 to override the default Tab5[0x16] value of 1 which will disable the default handling for the $5A, $60, $61, $62, $66, $67, $68, $69, $6A pump conditions during a pulse delivery, so that they will not generate a Pod fault if this condition isn't found to be cleared in a subsequent pulse delivery in the next 30 minutes. (Happened a lot in early Loop testing) The KK byte should be set to 00 to keep Tab5[0x17] at its default value (0) If this value is set to 01, the Pod will make a number of adjustments to various internal variables and a countdown timer which are used during the delivery of an immediate bolus. """ msgDict['msgMeaning'] = 'cnfgDelivFlags' msgDict['nonce'] = hex(combineByte(byteList[2:6])) msgDict['JJ(1)'] = byteList[6] msgDict['KK(0)'] = byteList[7] return msgDict
def parse_0202(byteList, msgDict): # extract information from the 02 response, type 2, return as a dictionary """ My example: msg = '0216020d00001b0d0a5b40108d03ff108f0000189708030d8010' OFF 1 2 3 4 5 6 7 8 9 10 1112 1314 1516 17 18 19 20 21 2223 02 16 02 0d 00 001b 0d 0a5b 40 108d 03ff 108f 00 00 18 97 08 030d 8010 The type 2 response provides information on a fault event. OFF 1 2 3 4 5 6 7 8 9 10 1112 1314 1516 17 18 19 20 21 2223 02 16 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY 02 (1 byte) [$0]: mtype value of 02 specifies status information 16 (1 byte) [$1]: mlen of a type 2 response is always $16 02 (1 byte) [$2]: type of this status format is 2 0J (1 byte) [$3]: Current Pod Progress value (00 thru 0F) 0K (1 byte) [$4]: bit mask for active insulin delivery 1: Basal active, exclusive of 2 bit 2: Temp basal active, exclusive of 1 bit 4: Immediate bolus active, exclusive of 8 bit 8: Extended bolus active, exclusive of 4 bit LLLL (2 bytes) [$5:$6]: 0.05U insulin pulses not delivered MM (1 byte) [$7]: message sequence number (saved B9>>2) NNNN (1 bytes) [$8:$9]: total # of pulses delivered PP (1 byte) [$A]: original logged fault event, if any QQQQ (2 bytes) [$B:$C]: fault event time in minutes since Pod activation or $ffff if unknown due to an unexpected MCU reset RRRR (2 bytes) [$D:$E]: # of 0.05U pulses remaining if <= 50U or $3ff if > 50U SSSS (2 bytes) [$F:$10]: minutes since Pod activation TT (1 byte) [$11]: bit mask of the active, unacknowledged alerts (1 << alert #) from the $19 Command, this bit mask is the same as the aaaaaaaaa bits in the $1D Response UU (1 byte) [$12]: 2 if there is a fault accessing tables VV (1 byte) [$13]: bits abbcdddd with information about logged fault event a: insulin state table corruption found during error logging bb: internal 2-bit variable set and manipulated in main loop routines c: immediate bolus in progress during error dddd: Pod progress at time of first logged fault event (same as 0X value) WW (1 byte): [$14] bits aabbbbbb aa: receiver low gain bbbbbb: radio RSSI 0X (1 byte): [$15] Pod progress at time of first logged fault event YYYY (2 bytes): [$16:$17] unknown """ mlen = byteList[1] if mlen != 0x16: msgDict['msgMeaning'] = '0x0202, unexpected size' return msgDict pod_progress = byteList[3] byte_4 = byteList[4] word_L = combineByte(byteList[5:7]) byte_M = byteList[7] word_N = combineByte(byteList[8:10]) byte_P = byteList[10] word_Q = combineByte(byteList[11:13]) word_R = combineByte(byteList[13:15]) word_S = combineByte(byteList[15:17]) byte_T = byteList[17] byte_U = byteList[18] byte_V = byteList[19] byte_W = byteList[20] byte_X = byteList[21] word_Y = combineByte(byteList[22:24]) cksm = combineByte(byteList[24:26]) # can extract gain and rssi from the byte_W # WW (1 byte): [$14] bits aabbbbbb # aa: receiver low gain # bbbbbb: radio RSSI gg = byte_W & 0xC0 >> 6 ss = byte_W & 0x3F msgDict['recvGain'] = gg msgDict['rssiValue'] = ss if pod_progress <= 9: msgDict['msgMeaning'] = 'Special Status Request Response' else: msgDict['msgMeaning'] = 'FaultEvent' # Since byte_2 was 2, update msgType msgDict['msgType'] = '0x0202' msgDict['pod_progress'] = pod_progress msgDict['extended_bolus_active'] = (byte_4 & 0x8) != 0 msgDict['immediate_bolus_active'] = (byte_4 & 0x4) != 0 msgDict['temp_basal_active'] = (byte_4 & 0x2) != 0 msgDict['basal_active'] = (byte_4 & 0x1) != 0 msgDict['pulses_not_delivered'] = word_L msgDict['insulin_not_delivered'] = getUnitsFromPulses(word_L) msgDict['seq_byte_M'] = byte_M msgDict['total_pulses_delivered'] = word_N msgDict['insulinDelivered'] = getUnitsFromPulses(word_N) msgDict['logged_fault'] = '{:#04x}'.format(byte_P) # msgDict['logged_fault'] = f'{byte_P: 0x%X}' msgDict['fault_time_minutes_since_pod_activation'] = word_Q resLevel = word_R & 0x3FF if resLevel == 0x3FF: msgDict['reservoir'] = '>50 u' else: msgDict['reservoir'] = getUnitsFromPulses(resLevel) msgDict['podOnTime'] = word_S msgDict['alerts_bit_mask'] = byte_T msgDict['table_fault'] = (byte_U == 2) msgDict['byte_V'] = byte_V msgDict['byte_W'] = byte_W msgDict['pod_progress_at_fault'] = byte_X msgDict['word_Y'] = word_Y msgDict['checkSum'] = cksm # but if logged_fault is 0x34, many registers are reset if msgDict['logged_fault'] == '0x34': msgDict['pulses_not_delivered'] = np.nan msgDict['pulsesTotal'] = np.nan msgDict['fault_time_minutes_since_pod_activation'] = np.nan msgDict['podOnTime'] = np.nan msgDict['pulses_not_delivered'] = np.nan return msgDict
def parse_1a16(byteList, msgDict): # extract information from the 1a16 temporary basal command """ This is the combination of two messages Note that for parsing the TB from Loop - we only need to do one fixed-rate, half-hour segment. So OK to cheat here Treat it as one master list Example: 1a0e726545dd0100a301384000150015160e000000d7007fbf7d00d7007fbf7d02bc First: 1a 0e 726545dd 01 00a3 01 3840 0015 0015 Second: 16 0e 00 00 00d7 007fbf7d 00d7 007fbf7d checksum: 02bc First half: 1a LL NNNNNNNN 01 CCCC HH SSSS PPPP napp [napp...] 16... 1a (1 byte): Mtype value of $1a specifies insulin schedule command LL (1 byte): Length of following bytes NNNNNNNN (4 bytes): Nonce, the 32-bit validator 01 (1 byte): TableNum of $01 specifies this is a temp basal command CCCC (2 bytes): CheckSum, is the sum of the bytes in the following 3 fields along with the bytes in the generated insulin schedule table HH (1 byte): Duration, # of resulting Half Hour entries in resulting temp basal insulin schedule table SSSS (2 bytes): SecsX8Left, $3840 (=14,400 dec = 30 x 60 x 8) for fixed rates PPPP (2 bytes): Pulses, # of pulses to deliver in the first half hour segment napp [napp...] (2 bytes per element): One or more InsulinScheduleElements describe temp basal rates Second half: 16 LL RR MM NNNN XXXXXXXX YYYY ZZZZZZZZ [YYYY ZZZZZZZZ...] 16 mtype LL length of message in bytes RR Reminder bits acrrrrrr a Acknowledgement beep c Confidence Reminder beeps rrrrrr # of minutes ($3c = 60 minutes) between Reminder beeps MM Always 0 NNNN Remaining 1/10th of a 0.05u pulse of insulin to deliver for first entry (i.e., # of pulses x 10). NNNN must be <= the first YYYY value, for fixed rate temp basal it will be same as the only YYYY value. XXXXXXXX (4 bytes): Delay until next 1/10th of a 0.05u pulse, expressed in microseconds. XXXXXXXX must be <= the first ZZZZZZZZ value YYYY (2 bytes): Total 1/10th of a 0.05u pulses of insulin for this schedule entry (i.e., # of pulses x 10). ZZZZZZZZ (4 bytes): Delay between 1/10th of a 0.05u pulse, expressed in microseconds. Example: 1.0 U/hr = 20 pulses per hour = 3 min (180 secs) between pulses = 18 secs for 1/10th of a pulse = 18,000,000 uS = 0x112a880 The XXXXXXXX and ZZZZZZZZ values have a minimum value of 0x30d40 (=200,000 decimal, i.e., 2 seconds between pulses) and a maximum value of 0x6b49d200 (=1,800,000,000 decimal, i.e., 5 hours between pulses). """ # 0 1 2 6 7 9 10 12 14 # First half: 1a LL NNNNNNNN 01 CCCC HH SSSS PPPP napp [napp...] 16... msgDict['msgMeaning'] = 'SetTempBasal' mlen = byteList[1] # use mlen to determine when the 16 follow on command starts # two values are 14 (0x0e) or 16 (0x10) msgDict['nonce'] = hex(combineByte(byteList[2:6])) TableNum = byteList[6] chsum = hex(combineByte(byteList[7:9])) hhEntries = byteList[9] # number of half hr entries, max is 24 = 12 hrs secsX8Left = combineByte(byteList[10:12]) # always 0x3840 pulsesPerHhr = combineByte(byteList[12:14]) # pulses per half hr segment # TODO - fix this section # if mlen == 0x0e: # numHalfHrSegs = 1 # all we expect with Loop # # ignore first napp - always indicate 1 half hour segment # elif mlen == 0x10: # napp1 = combineByte(byteList[14:16]) # napp2 = combineByte(byteList[16:18]) # numHalfHrSegs = 10 # place holder, get from masking napp1 + napp2 # else: # print('mlen is not valid in msg') # print(msg) # return msgDict # count below is true iff mlen is 0x0e # 16 17 18 19 20 22 26 28 # Second half: 16 LL RR MM NNNN XXXXXXXX YYYY ZZZZZZZZ [YYYY ZZZZZZZZ...] xtype = byteList[2+mlen] xlen = byteList[3+mlen] reminders = byteList[4+mlen] MM = byteList[5+mlen] # Always 0 firstEntryX10pulses = combineByte(byteList[(6+mlen):(8+mlen)]) firstDelayMicroSec = combineByte(byteList[(8+mlen):(12+mlen)]) totalEntryX10pulses = combineByte(byteList[(12+mlen):(14+mlen)]) delayMicroSec = combineByte(byteList[(14+mlen):(18+mlen)]) if firstEntryX10pulses != totalEntryX10pulses: print('Warning - temp basal not properly configured, # pulses') if firstDelayMicroSec != delayMicroSec: print('Warning - temp basal not properly configured, # microsec') msgDict['TableNum'] = TableNum msgDict['chsum'] = chsum msgDict['hhEntries'] = hhEntries msgDict['secsX8Left'] = secsX8Left msgDict['pulsesPerHhr'] = pulsesPerHhr msgDict['pulsesPerHhr'] = pulsesPerHhr msgDict['xtype'] = xtype msgDict['xlen'] = xlen msgDict['reminders'] = reminders msgDict['always0'] = MM msgDict['firstEntryX10pulses'] = firstEntryX10pulses msgDict['firstDelayMicroSec'] = firstDelayMicroSec msgDict['totalEntryX10pulses'] = totalEntryX10pulses msgDict['delayMicroSec'] = delayMicroSec msgDict['pulses_in_TB_halfHr'] = 0.1 * firstEntryX10pulses # u per pulse * half hours per hour * number of pulses = rate u/hr basalRate = Decimal(0.05 * 2 * 0.1 * firstEntryX10pulses) x = round(basalRate, 2) msgDict['temp_basal_rate_u_per_hr'] = float(x) return msgDict
def parse_1f(byteList, msgDict): # extract information from the 1f cancel command """ Command $1F is the Cancel command. It has the following format: OFF 1 2 3 4 5 6 1f 05 NNNNNNNN AX 1f 05 22c1cf8c 02 80f7 1f (1 byte): Mtype value of $1f specifies a cancel command 05 (1 byte): The cancel command always has a fixed length of 05 NNNNNNNN (4 bytes): Nonce, the 32-bit validator (random looking numbers) AX (1 byte): Command byte of the form aaaa0bcd: The aaaa (A) nibble in the range of (0..8) and is the type of the Pod alarm/beep type to sound. An aaaa value = 0 is no alarm = 2 two quick beeps = 6 one longer beep Values of A larger than 8 are out of range and can lock up a Pod with a constant beep. The b ($04) bit being set will cancel any ongoing bolus and will turn off the RR reminder variables set from the $17 Bolus Extra command. The c ($02) bit being set will cancel any ongoing temporary basal and will turn off the RR reminder variables set from the $16 Temp Basal command. The d ($01) bit being set will cancel the basal program and will turn off the RR reminder variables set from the $13 Basal Schedule command. The Pod responds to the $1F command with a $1D status message. """ commandByte = byteList[6] alertValue = (commandByte >> 4) & 0xF cancelCode = commandByte & 0x07 cancelBolus = (cancelCode & 0x04) != 0 cancelTB = (cancelCode & 0x02) != 0 suspend = (cancelCode & 0x01) != 0 cnxByteStr = '{:x}'.format(cancelCode) msgDict['msgType'] = msgDict['msgType'] + cnxByteStr if cancelCode == 0x07: # msgDict['msgType'] = '0x1f7' msgDict['msgMeaning'] = 'CancelAll' elif cancelBolus: # msgDict['msgType'] = '0x1f4' msgDict['msgMeaning'] = 'CancelBolus' elif cancelTB: # msgDict['msgType'] = '0x1f2' msgDict['msgMeaning'] = 'CancelTB' elif suspend: # msgDict['msgType'] = '0x1f1' msgDict['msgMeaning'] = 'SuspendPod' else: msgDict['msgMeaning'] = 'CancelNone' msgDict['commandByte'] = commandByte msgDict['alertValue'] = alertValue msgDict['cancelCode'] = cancelCode msgDict['cancelBolus'] = cancelBolus msgDict['cancelTB'] = cancelTB msgDict['suspend'] = suspend msgDict['nonce'] = hex(combineByte(byteList[2:6])) return msgDict
def parse_1a17(byteList, msgDict): """ This is the combination of two messages Note that for parsing the TB from Loop - we only need to do one fixed-rate, half-hour segment. So OK to cheat here Treat it as one master list Example: '1a0e1b8fd9f70201ab01089000890089170d00055a00030d400000000000008359' First: 1a 0e 1b8fd9f7 02 01ab 01 0890 0089 0089 Second: 17 0d 00 055a00 030d4000 0000 0000008359 First half: 1a LL NNNNNNNN 02 CCCC HH SSSS PPPP 0ppp [napp...] 17... 1a (1 byte): Mtype value of $1a specifies insulin schedule command LL (1 byte): Length, == $0e for normal bolus >= $10 for extended bolus NNNNNNNN (4 bytes): Nonce, the 32-bit validator 02 (1 byte): TableNum of $02 specifies this is a bolus command CCCC (2 bytes): CheckSum, is the sum of the bytes in the following 3 fields along with the bytes in the generated insulin schedule table HH (1 byte): Duration, # of resulting Half Hour entries always 1 for Loop (no extended bolus) SSSS (2 bytes):PPPP times $10 (for a standard two seconds between pulse immediate bolus) or times 8 (for an one second between pulse bolus during Pod startup) PPPP (2 bytes): Pulses, # of pulses to deliver in first half hour napp [napp...] (2 bytes per element) Second half: 17 LL RR NNNN XXXXXXXX YYYY ZZZZZZZZ The 0x17 command is the follow-on command for the Command 0x1A Table 2 case to deliver either a normal and/or an extended bolus. This command always immediately follows the 0x1A command with Table 2 (bolus case) in the same message. The generic format of the 0x17 bolus follow-on command for a command 1A Table 2 bolus is: 17 LL RR NNNN XXXXXXXX YYYY ZZZZZZZZ 17 (1 byte): Mtype value of $17 specifies bolus follow-on command for a 1A command Table 2 LL (1 byte): Length of the following bytes for this command, always $0d RR (1 byte): Reminder bits acrrrrrr (typical PDM values $00, $3c, $40 and $7c): a bit ($80) Acknowledgement beep (command received), not used by PDM c bit ($40) Confidence Reminder beeps (start and end of delivery) rrrrrr (6 bits) # of minutes ($3c = 60 minutes) between Program Reminder beeps NNNN (2 bytes): # of 0.05U pulses x 10 XXXXXXXX (4 bytes): delay in 100 kHz to start of delivery, minimum value of $30D40 = 200,000 (2 seconds) during normal Pod use and minimum value of $186A0 = 100,000 (1 second) during Pod startup. YYYY (2 bytes): # of 0.05U pulses x 10 to deliver, extended bolus ZZZZZZZZ (4 bytes): delay interval in 100 kHz between pulses for extended bolus interval NNNN will be 0 if there is no immediate bolus. YYYY and ZZZZZZZZ will both be 0 if there is no extended bolus. The XXXXXXXX and ZZZZZZZZ values have a minimum value of 0x30d40 (=200,000 decimal, i.e., 2 seconds between pulses) and a maximum value of 0x6b49d200 (=1,800,000,000 decimal, i.e., 5 hours between pulses). """ # 0 1 2 6 7 9 10 12 14 # First half: 1a LL NNNNNNNN 02 CCCC HH SSSS PPPP 0ppp [napp...] 17... msgDict['msgMeaning'] = 'SetBolus' msgDict['autoBolus'] = False TableNum = byteList[6] chsum = combineByte(byteList[7:9]) hhEntries = byteList[9] secsX8Left = combineByte(byteList[10:12]) pulsesPerHhr = combineByte(byteList[12:14]) pulse0 = combineByte(byteList[14:16]) # only immediate bolus used by Loop - so done with first half # 16 17 18 19 21 25 27 # Second half: 17 LL RR NNNN XXXXXXXX YYYY ZZZZZZZZ xtype = byteList[16] xlen = byteList[17] reminders = byteList[18] msgDict['autoBolus'] = 0x3f & reminders == 0x3f promptTenthPulses = combineByte(byteList[19:21]) promptDelay = combineByte(byteList[21:25]) extendedTenthPulses = combineByte(byteList[25:27]) extendedDelay = combineByte(byteList[27:31]) if extendedTenthPulses != 0: print('Warning - bolus not properly configured, extended pulses not 0') msgDict['prompt_bolus_u'] = getUnitsFromPulses(pulsesPerHhr) msgDict['nonce'] = hex(combineByte(byteList[2:6])) msgDict['TableNum'] = TableNum msgDict['chsum'] = chsum msgDict['hhEntries'] = hhEntries msgDict['secsX8Left'] = secsX8Left msgDict['pulsesPerHhr'] = pulsesPerHhr msgDict['pulse0'] = pulse0 msgDict['xtype'] = xtype msgDict['xlen'] = xlen msgDict['reminders'] = reminders msgDict['promptTenthPulses'] = promptTenthPulses msgDict['promptDelay'] = promptDelay msgDict['extendedTenthPulses'] = extendedTenthPulses msgDict['extendedDelay'] = extendedDelay return msgDict
def parse_01(byteList, msgDict): # pod response to 0x07 or 0x03 """ My example: # 0x07 response: msg = '011502090002090002010000aecb0006df6ea61f0747ca037c' # 0x03 response: msg = '011b13881008340a5002090002090002030000aecb0006df6e1f0747ca0208' Response 01 message with mlen $15 is returned from 07 Command Assign ID: OFF 1 2 3 4 5 6 7 8 9 10111213 14151617 18 19202122 01 15 MXMYMZ IXIYIZ 02 0J LLLLLLLL TTTTTTTT GS IIIIIIII 01 (1 byte) [0]: mtype value of 01 specifies Version Response command 15 (1 byte) [1]: mlen of $15 is this format (the 07 Command response) MXMYMZ (3 bytes) [2:4]: PM MX.MY.MZ IXIYIZ (3 bytes) [5:7]: PI IX.IY.IZ 02 (1 byte) [8]: always 2 (at least for PM == PI == 2.7.0) 0J (1 byte) [9]: Pod Progress State, typically 02, but possibly 01 LLLLLLLL (4 bytes) [$A:$D]: Pod Lot TTTTTTTT (4 bytes) [$E:$11]: Pod TID GS (1 byte) [$12]: ggssssss where gg = two bits of receiver gain, ssssss = 6 bits of rssi value IIIIIIII (4 bytes) [$13:$16]: ID (Pod address) as given by 07 Command Response 01 message with mlen $1b is returned from 03 Command Setup Pod: OFF 1 2 3 4 5 6 7 8 91011 121314 15 16 17181920 21222324 25262728 01 1b 13881008340A50 MXMYMZ IXIYIZ 02 0J LLLLLLLL TTTTTTTT IIIIIIII 01 (1 byte) [0]: mtype value of 01 specifies Version Response command 1b (1 byte) [1]: mlen of $1b is this format (the 03 Command response) 13881008340A50 (7 bytes) [2:8]: fixed byte sequence of unknown meaning MXMYMZ (3 bytes) [9:$B]: PM MX.MY.MZ IXIYIZ (3 bytes) [$C:$E]: PI IX.IY.IZ 02 (1 byte) [$F]: always 2 (for PM == PI == 2.7.0) 0J (1 byte) [9]: Pod Progress State, should be 03 for this response LLLLLLLL (4 bytes) [$11:$14]: Pod Lot TTTTTTTT (4 bytes) [$15:$18]: Pod TID IIIIIIII (4 bytes) [$19:$1C]: Connection ID (Pod address) """ # put place holders for msgDict values I want near beginning msgDict['pod_progress'] = -1 # will be overwritten msgDict['podAddr'] = 'tbd' # will be overwritten if byteList[1] == 0x15: msgDict['msgMeaning'] = 'IdAssigned' pmVer = byteList[2:5] piVer = byteList[5:8] pprog = byteList[9] podLot = combineByte(byteList[10:14]) podTid = combineByte(byteList[14:18]) gS = byteList[18] podAddr = combineByte(byteList[19:23]) # mask gS gg = gS & 0xC0 >> 6 ss = gS & 0x3F msgDict['msgType'] = '0x0115' msgDict['recvGain'] = gg msgDict['rssiValue'] = ss elif byteList[1] == 0x1b: msgDict['msgMeaning'] = 'PodSetupOK' fixedWord = byteList[2:9] pmVer = byteList[9:12] piVer = byteList[12:15] pprog = byteList[16] podLot = combineByte(byteList[17:21]) podTid = combineByte(byteList[21:25]) podAddr = combineByte(byteList[25:29]) msgDict['msgType'] = '0x011b' msgDict['fixedWord'] = fixedWord # fill in rest of common msgDict msgDict['pmVersion'] = versionString(pmVer) msgDict['piVersion'] = versionString(piVer) msgDict['pod_progress'] = pprog msgDict['lot'] = podLot msgDict['tid'] = podTid msgDict['podAddr'] = hex(podAddr) return msgDict
def parse_1d(byteList, msgDict): # extract information from the 1d response and return as a dictionary """ The $1D status response is sent from the Pod to provide information on its internal state, including insulin delivery, unacknowledged alerts, Pod active time, reservoir level, etc. as the normal response to non-specialized commands. This page explains how the different values are packed into the $1D status response. It is the only command without an mlen value - always fixed length of 12 bytes The $1D status response has the following form: 00 01 02030405 06070809 1d SS 0PPPSNNN AATTTTRR # byte 0 - 1d # byte 1 - nibble 1a, nibble 1b nibble 1a a 8: Extended bolus active, exclusive of 4 bit b 4: Immediate bolus active, exclusive of 8 bit c 2: Temp basal active, exclusive of 1 bit d 1: Basal active, exclusive of 2 bit nibble 1b sequence number 0 to F # byte 2:5 = 0PPPSNNN 0PPPSNNN dword = 0000 pppp pppp pppp psss snnn nnnn nnnn 0000 4 zero bits ppppppppppppp 13 bits, Total 0.05U insulin pulses ssss 4 bits, message sequence number (saved B9>>2) nnn nnnn nnnn 11 bits, 0.05U Insulin pulses not delivered if cancelled by user # byte 6:9 = AATTTTRR AATTTTRR dword = faaa aaaa attt tttt tttt ttrr rrrr rrrr f 1 bit, 0 or 1 if the Pod has encountered fault event $14 aaaaaaaa 8 bits, bit mask of the active, unacknowledged alerts (1 << alert #) from the Command 19 Configure Alerts; this bit mask is the same as the TT byte in the 02 Error Response Type 2 ttttttttttttt 13 bits, Pod active time in minutes rrrrrrrrrr 10 bits, Reservoir 0.05U pulses remaining (if <= 50U) or $3ff (if > 50U left) """ msgDict['msgMeaning'] = 'PodStatus' # only fixed length message where byte_1 has a different meaning byte_1 = byteList[1] msgDict['pod_progress'] = byte_1 & 0xF dword_3 = combineByte(byteList[2:6]) dword_4 = combineByte(byteList[6:10]) cksm = hex(combineByte(byteList[10:12])) msgDict['podOnTime'] = (dword_4 >> 10) & 0x1FFF msgDict['reservoir'] = '' # placeholder for desired order # get pulses and units of insulin delivered pulsesDelivered = (dword_3 >> 15) & 0x1FFF insulinDelivered = getUnitsFromPulses(pulsesDelivered) msgDict['pulsesTotal'] = pulsesDelivered msgDict['insulinDelivered'] = insulinDelivered msgDict['seqNum'] = (dword_3 >> 11) & 0xF msgDict['extended_bolus_active'] = (byte_1 >> 4 & 0x8) != 0 msgDict['immediate_bolus_active'] = (byte_1 >> 4 & 0x4) != 0 msgDict['temp_basal_active'] = (byte_1 >> 4 & 0x2) != 0 msgDict['basal_active'] = (byte_1 >> 4 & 0x1) != 0 msgDict['pod_progress_meaning'] = getPodProgressMeaning(byte_1 & 0xF) # get pulses and units of insulin NOT delivered pulsesNot = dword_3 & 0x007F insulinNot = getUnitsFromPulses(pulsesNot) msgDict['pulses_not_delivered'] = pulsesNot msgDict['insulin_not_delivered'] = insulinNot msgDict['fault_bit'] = (dword_4 >> 31) msgDict['alerts_bit_mask'] = (dword_4 >> 23) & 0xFF if (dword_4 & 0x3FF) == 0x3FF: msgDict['reservoir'] = '>50 u' else: pulses = dword_4 & 0x3FF insulin = getUnitsFromPulses(pulses) msgDict['reservoir'] = insulin msgDict['mlen'] = 12 msgDict['checkSum'] = cksm return msgDict
def parse_1a13(byteList, msgDict): # extract information from the 1a13 basal command """ This is the combination of two messages But because the basal schedule probably pretty complicated, will cheat First Half: 1a LL NNNNNNNN 00 CCCC HH SSSS PPPP napp napp napp [napp...] 13... 1a (1 byte): Mtype value of $1a specifies insulin schedule command LL (1 byte): Length of following bytes NNNNNNNN (4 bytes): Nonce, the 32-bit validator 00 (1 byte): TableNum of $00 specifies this is a basal schedule command CCCC (2 bytes): CheckSum, is the sum of the bytes in the following 3 fields along with the bytes in generated insulin schedule table HH (1 byte): Current Half Hour of the day (0 to 47) SSSS (2 bytes): SecsX8Left until end of hour hour (max $3840) (=14,400 dec = 30 x 60 x 8) for fixed rates PPPP (2 bytes): Pulses, # of pulses to deliver in this half hour napp napp napp etc: 2 bytes per napp element of the basal table n+1 half hour entries a adds extra pulse alternate half hours pp number of pulses in the schedule Second half: 13 LL RR MM NNNN XXXXXXXX YYYY ZZZZZZZZ [YYYY ZZZZZZZZ ...] 13 mtype LL length of message in bytes RR Reminder bits acrrrrrr a Acknowledgement beep c Confidence Reminder beeps rrrrrr # of minutes ($3c = 60 minutes) between Reminder beeps MM Number of schedule entries NNNN Remaining 1/10th of a 0.05u pulse of insulin to deliver for first entry (i.e., # of pulses x 10). NNNN must be <= the first YYYY value, for fixed rate temp basal it will be same as the only YYYY value. XXXXXXXX (4 bytes): Delay until next 1/10th of a 0.05u pulse, expressed in microseconds. XXXXXXXX must be <= the first ZZZZZZZZ value YYYY (2 bytes): Total 1/10th of a 0.05u pulses of insulin for this schedule entry (i.e., # of pulses x 10). ZZZZZZZZ (4 bytes): Delay between 1/10th of a 0.05u pulse, expressed in microseconds. Example: 1.0 U/hr = 20 pulses per hour = 3 min (180 secs) between pulses = 18 secs for 1/10th of a pulse = 18,000,000 uS = 0x112a880 The XXXXXXXX and ZZZZZZZZ values have a minimum value of 0x30d40 (=200,000 decimal, i.e., 2 seconds between pulses) and a maximum value of 0x6b49d200 (=1,800,000,000 decimal, i.e., 5 hours between pulses). """ # 0 1 2 6 7 9 10 12 14 15 ... to mlen # First half: 1a LL NNNNNNNN 00 CCCC HH SSSS PPPP napp napp 13... msgDict['msgMeaning'] = 'SetBasalSch' mlen = byteList[1] msgDict['nonce'] = hex(combineByte(byteList[2:6])) TableNum = byteList[6] chsum = combineByte(byteList[7:9]) currentHH = byteList[9] secsX8Left = combineByte(byteList[10:12]) hhpulses = combineByte(byteList[12:14]) nappArray = combineByte(byteList[14:mlen]) # # Second half: 13 LL RR MM NNNN XXXXXXXX YYYY ZZZZZZZZ [YYYY ZZZZZZZZ ...] xtype = byteList[mlen+2] xlen = byteList[mlen+3] reminders = byteList[mlen+4] scheduleEntryIndex = byteList[mlen+5] msgDict['TableNum'] = TableNum msgDict['chsum'] = chsum msgDict['currentHH'] = currentHH msgDict['secsX8Left'] = secsX8Left msgDict['hhpulses'] = hhpulses msgDict['nappArray'] = hex(nappArray) msgDict['xtype'] = xtype msgDict['xlen'] = xlen msgDict['reminders'] = reminders msgDict['scheduleEntryIndex'] = scheduleEntryIndex return msgDict
def parse_19(byteList, msgDict): """ Command $19 can used to configure a number of different alerts. This command is used multiple times during initial setup and for various other configuration situations like changing the Low Reservoir or Replace Pod Soon alerts and for Confidence Reminders. The $19 command can also follow a Command 1F Cancel-Request, when the user issues a suspend. 19 LL NNNNNNNN IVXX YYYY 0J0K [IVXX YYYY 0J0K]... 19 (1 byte): Mtype of $19 specifies a Configure Alerts Command LL (1 byte): Length of command (typically $0a, $10 or $16) NNNNNNNN (4 bytes): Nonce, 32-bit validator (random looking numbers) IVXX (2 bytes): bit format 0iiiabcx xxxxxxxx as follows: 0iii is the 3-bit alert #. In the $1D Status Response aaaaaaaa bits and in the TT byte of the $02 Pod Information Response Type 2, active, unacknowledged alerts are returned as a bit mask (1 << alert #). The a ($0800) bit being set indicates that this alert is active. The b ($0400) bit being set indicates this alert is for setting a low reservoir alert and a not time based alert. The c ($0200) bit being set indicates that this alert is for the Auto-Off function. x xxxxxxxx is a 9-bit value for the alert duration in minutes YYYY (2 bytes): 14-bit alert value If b bit is 0, YYYY is the number of minutes from now before alerting, maximum value 4,800 (i.e., 80 hours). If b bit is 1, YYYY is the number of units for low reservoir alert x 10 (i.e., the # of 0.05U pulses / 2), maximum value 500 (i.e., 50U x 10). 0J0K (2 bytes): 12-bit beep word consisting of two bytes 0J and 0K. The J nibble is a beep repeat pattern value: 0 - Beep once 1 - Beep every minute for 3 minutes and repeat every 60 minutes 2 - Beep every minute for 15 minutes 3 - Beep every minute for 3 minutes and repeat every 15 minutes 4 - Beep at 2, 5, 8, 11, ... 53, 56, 59 minutes 5 - Beep every 60 minutes 6 - Beep every 15 minutes 7 - Beep at 14, 29, 44 and 59 minutes 8 - Beep every 5 minutes The K nibble is a beep type value from 0 (silent) to 8 as given in Beep types: 0 - No Sound 1 - BeepBeepBeepBeep 2 - BipBeep BipBeep BipBeep BipBeep 3 - BipBip 4 - Beep 5 - BeepBeepBeep 6 - Beeeeeep 7 - BipBipBip BipBipBip 8 - BeeepBeeep The three-word entries can be repeated an arbitrary number of times for setting multiple alerts with the same $19 command. The command length LL for a single alert $19 command is $0a, for a double alert $19 command is $10, and for a triple alert $19 command is $16. """ msgDict['msgMeaning'] = 'cnfgAlerts' msgDict['nonce'] = hex(combineByte(byteList[2:6])) return msgDict