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
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #8
0
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
Exemple #10
0
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
Exemple #14
0
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