Hla extension to decode I2C not working

I’m trying to write a Hla python script using Saleae provided examples to decode and capture data on a data byte and a NAK [0x44 and NAK] for an I2C issue I’m trying to debug (bq40z50 TI Fuel Gauge).
I’m getting the following error:

i2c_salaeae_nak_ext error - AttributeError(“Hla’ object has no attribute 'decode”)

I’m using Logic Pro 16 Version 2.3.41 - great product!!

Hi @cbishop, excited to hear you’re using our HLA API!

It sounds like your HLA class, which should extend HighLevelAnalyzer, is missing a required method, “decode”. If you post your code (preferably to a git repo) we could take a look for you.

You can find some API documentation here:

It sounds like you might have accidentally renamed or removed the decode method from your class, if you started with the template our software provides.

Thanks, Mark. I figured out some of it. I have (2) questions:

  1. I’m trying to output an HLA frame when I see a “Setup Write to [0x0B] + ACK” and “0x44 + NAK”. I haven’t been able to figure out the syntax for the "Setup Write to [0x0B]. Is there a frame.address[‘data’] type?
  2. Is there a way to STOP the capture when I detect this NAK?

Here’s the script/code (this finds the data “0x0B and ack” properly):

def __init__(self):
    pass

def decode(self, frame: AnalyzerFrame):
    i=0
    # The 'data' field only contains one byte [8 bits]
    try:
        ch = frame.data['data'].decode('ascii')
        ack = frame.data['ack']
    except:
        # Not an ASCII character
        return

    if ch in chr(0x0B) and not ack:

        return AnalyzerFrame('type', frame.start_time, frame.end_time, {
            'input_type': frame.type

This works:
image

I’m trying to put an AnalyzerFrame on this:

Thank you

First off, you can place print("") statements in your HLA, and they will appear in the terminal view in the app. The terminal view is accessed on the analyzer sidebar, by clicking the little terminal icon:
image
That should help a lot for debugging.

Second, you can’t stop the capture from an HLA, unfortunately. However, you can create a unique frame that you can search for in the sidebar.

For your first question, first take a look at the I2C frame format here: I2C - Frame Format - Saleae Support

The main thing your code is missing is that it’s not checking the frame type first.

I modified your example a bit to show how frame type is used:

    def __init__(self):
        pass

    def decode(self, frame: AnalyzerFrame):
        i = 0
        # The 'data' field only contains one byte [8 bits]
        ch = None
        type = None
        ack = None
        if frame.type == 'address':
            address = frame.data['address'][0]
            ch = address.decode('ascii')
            print('HLA parsed an address frame: ' + ch)
            type = 'address'
        elif frame.type == 'data':
            data = frame.data['data']
            ch = data.decode('ascii')
            print('HLA parsed an data frame: ' + ch)
            type = 'data'
        else:
            print("unrecognized frame type, ignoring")
            return
        try:
            ack = frame.data['ack']
        except:
            # no ACK bit. this only happens if there was an error decoding the frame, e.g. bad I2C data.
            print('ack bit missing, skipping this frame.')
            return

        if ch in chr(0x0B) and not ack:

            return AnalyzerFrame(type, frame.start_time, frame.end_time, {
                'input_type': frame.type
            })

Thanks, Mark.
I’m seeing this error:
image

ch = frame.data[‘address’][0].decode(‘ascii’)

Any ideas?
Thank you.

Oh sorry I didn’t test that code. frame.data[‘address’] is a bytes object, which means frame.data[‘address’][0] is a number. To use .decode, you will need to remove the [0] I think. Let me know if you still have trouble with it.

Hi Mark,
In the code below, I’m able to place a custom frame into the HLA for the address OR the data, is there a way to put the custom frame over both? I’m trying to catch a Write(0x0B) that is followed by a 0x44 NAK. Our master sends data every second and it takes about (2) days to see the issue occur.

High Level Analyzer

For more information and documentation, please go to https://support.saleae.com/extensions/high-level-analyzer-extensions

Craig Bishop (looking for 0x44 + NAK on I2C/SMBUS on BQ charger chip).

from saleae.analyzers import HighLevelAnalyzer, AnalyzerFrame, StringSetting, NumberSetting, ChoicesSetting

High level analyzers must subclass the HighLevelAnalyzer class.

class Hla(HighLevelAnalyzer):
# List of settings that a user can set for this High Level Analyzer.
search_for = StringSetting()
# An optional list of types this analyzer produces, providing a way to customize the way frames are displayed in Logic 2.
result_types = {
‘mytype’: {
‘format’: ‘Output type: {{type}}, Input type: {{data.input_type}}’
}
}

def __init__(self):
    pass

def decode(self, frame: AnalyzerFrame):
    i = 0
    # The 'data' field only contains one byte [8 bits]
    ch = None
    ch1 = None
    type = None
    ack = None
    if frame.type == 'address':
        #ch = frame.data['address'].decode('ascii')
        print('HLA parsed an address frame: ' + 'address')
        type = 'address'
    elif frame.type == 'data':
        print('HLA parsed a data frame:')
        type = 'data'
    else:
        #print("unrecognized frame type, ignoring")
        return

    try:
        ack = frame.data['ack']

ch1 = frame.data[‘data’].decode(‘ascii’)

    except:
        # no ACK bit. this only happens if there was an error decoding the frame, e.g. bad I2C data.
        return

    if frame.type == 'address' and frame.data['read'] == False and ack:
        return AnalyzerFrame(type, frame.start_time, frame.end_time, {
            'input_type': frame.type

if ch1 in chr(0x44) and ack:

return AnalyzerFrame(type, frame.start_time, frame.end_time, {

‘input_type’: frame.type

        })


or I can capture this:

Hi @cbishop,

You can do this by using the start time of the address frame (frame.start_time) with the end time of the data frame (frame.end_time). Modifying your example, something like this should work (I haven’t tested it):

def __init__(self):
    self.previous_frame = None

def decode(self, frame: AnalyzerFrame):
    # The 'data' field only contains one byte [8 bits]
    ch1 = None
    ack = None

    previous_frame = self.previous_frame

    # Store the previous frame immediately in case we exit early
    self.previous_frame = frame

    if frame.type not in ('address', 'data'):
        return

    if previous_frame.type == 'address' and previous_frame.data['read'] == False and getattr(previous_frame, 'ack', False):
        ack = getattr(frame, 'ack', False)
        ch1 = frame.data['data'].decode('ascii')
        if ch1 in chr(0x44) and ack:
            return AnalyzerFrame('yourdatatype', previous_frame.start_time, frame.end_time, {})

This works (tested on hardware), thanks for all the help. The “previous frame” was the critical piece I was missing. I hope this helps others interested in capturing strange I2C/SMBUS ACKs [or NAKs].

class Hla(HighLevelAnalyzer):
# List of settings that a user can set for this High Level Analyzer.
search_for = StringSetting()
# An optional list of types this analyzer produces, providing a way to customize the way frames are displayed in Logic 2.
result_types = {
‘mytype’: {
‘format’: ‘Output type: {{type}}, Input type: {{data.input_type}}’
}
}

def __init__(self):
    self.previous_frame = None
    pass

def decode(self, frame: AnalyzerFrame):
    i = 0
    # The 'data' field only contains one byte [8 bits]
    ch = None
    ch1 = None
    type = None
    ack = None

    if frame.type == 'address':
        #ch = frame.data['address'].decode('ascii')
        print('HLA parsed an address frame: ' + 'address')
        type = 'address'
    elif frame.type == 'data':
        print('HLA parsed a data frame:')
        type = 'data'
    else:
        #print("unrecognized frame type, ignoring")
        return

    previous_frame = self.previous_frame
    self.previous_frame = frame


    try:
        ack = frame.data['ack']
        ch1 = frame.data['data'].decode('ascii')
    except:
        # no ACK bit. this only happens if there was an error decoding the frame, e.g. bad I2C data.
        return

    if previous_frame.type == 'address' and previous_frame.data['read'] == False and ack:
        if frame.type == 'data' and ch1 in chr(0x44) and ack:
            return AnalyzerFrame('type', previous_frame.start_time, frame.end_time, {
            })

Write to 0x0B followed by 0x44+ACK: