High-Level Analyzer (HLA) Extensions
Learn how to modify your new High Level Analyzer
This guide assumes that you have familiarity with the Python programming language. It is what will be used to customize our HLA.

Overview

This guide assumes you have generated a new High-Level Analyzer. In this guide you will learn about:
  1. 1.
    The files included in the HLA template extension and what they are.
  2. 2.
    The different parts of HighLevelAnalyzer.py.
  3. 3.
    How to process input analyzer frames and output new analyzer frames.

High Level Analyzer Files

In your new High Level Analyzer (HLA) extension folder you will find 3 files:
  • README.md
    • Documentation for your extension, shown within Logic 2 when you select an extension, and what users will see if you put your extension on the Marketplace.
  • extension.json
    • Every extension must have this file in its root directory.
    • Contains metadata about the extension, and the HLAs and Measurement scripts that are included with the extension.
    • See Extension File Format for more information.
  • HighLevelAnalyzer.py
    • Python source code for your HLA.
For the purposes of this document, we will be focusing on HighLevelAnalyzer.py

HighLevelAnalyzer.py Breakdown

Let's break down the contents of HighLevelAnalyzer.py .
HighLevelAnalyzer.py
1
from saleae.analyzers import HighLevelAnalyzer, AnalyzerFrame, StringSetting, NumberSetting, ChoicesSetting
2
3
class MyHla(HighLevelAnalyzer):
4
5
# Settings:
6
my_string_setting = StringSetting()
7
my_number_setting = NumberSetting(min_value=0, max_value=100)
8
my_choices_setting = ChoicesSetting(['A', 'B'])
9
10
# Output formats
11
result_types = {
12
'mytype': {
13
'format': 'Output type: {{type}}, Input type: {{data.input_type}}'
14
}
15
}
16
17
# Initialization
18
def __init__(self):
19
print("Settings:", self.my_string_setting,
20
self.my_number_setting, self.my_choices_setting)
21
22
# Data Processing
23
def decode(self, frame: AnalyzerFrame):
24
return AnalyzerFrame('mytype', frame.start_time, frame.end_time, {
25
'input_type': frame.type
26
})
Copied!

Imports

1
from saleae.analyzers import HighLevelAnalyzer, AnalyzerFrame, StringSetting, NumberSetting, ChoicesSetting
Copied!

Declaration and Settings

All HLAs must subclass HighLevelAnalyzer, and additionally output AnalyzerFrames. The Setting classes are included so we can specify settings options within the Logic 2 UI.
1
class MyHla(HighLevelAnalyzer):
2
my_string_setting = StringSetting(label='My String')
3
my_number_setting = NumberSetting(label='My Number', min_value=0, max_value=100)
4
my_choices_setting = ChoicesSetting(label='My Choice', ['A', 'B'])
Copied!
This declares our new HLA class, which extends from HighLevelAnalyzer, and 3 settings options that will be shown within the Logic 2 UI.

Output formats

1
result_types = {
2
'mytype': {
3
'format': 'Output type: {{type}}, Input type: {{data.input_type}}'
4
}
5
}
Copied!
This specifies how we want output AnalyzerFrames to be displayed within the Logic 2 UI. We will come back to this later.

Initialization

1
def __init__(self):
2
print("Settings:", self.my_string_setting,
3
self.my_number_setting, self.my_choices_setting)
Copied!
This is called when your HLA is first created, before processing begins. The values for the settings options declared at the top of this class will be available as instance variables here. In this case, the settings values will be printed out and visible within the Logic 2 terminal view.

Data Processing

1
def decode(self, frame: AnalyzerFrame):
2
return AnalyzerFrame('mytype', frame.start_time, frame.end_time, {
3
'input_type': frame.type
4
})
Copied!
This is where the bulk of the work will be done. This function is called every time the input to this HLA produces a new frame. It is also where we can return and output new frames, to be displayed within the Logic 2 UI. In this case we are outputting a new frame of type 'mytype', which spans the same period of time as the input frame, and has 1 data value 'input_type' that contains the value of the type of the input frame.

HLA Debugging Tips

Although we don't have the ability to attach debuggers to Python extensions at the moment, here are some suggestions to help debug your HLA.
  • Use print() statements to print debug messages to our in-app terminal. More information on our in-app terminal can be found below.
  • Use the Wall Clock Format and Timing Markers to locate the exact frame listed in your error message.
  • Use the reload source button in the app to quickly re-run your HLA after editing your source code.
"Reload Extension" button

Example - Writing an HLA to search for a value

Now that we've gone over the different parts of an HLA, we will be updating our example HLA to search for a value from an Async Serial analyzer.

Example Data

In the Extensions Quickstart you should have downloaded and opened a capture of i2c data. For this quickstart we will be using a capture of Async Serial data that repeats the message "Hello Saleae".
hla-quickstart.zip
10KB
Binary
Async Serial Example Data

Remove Unneeded Code

To start, let's remove most of the code from the example HLA, and replace the settings with a single search_for setting, which we will be using later.
1
from saleae.analyzers import HighLevelAnalyzer, AnalyzerFrame, StringSetting
2
3
class MyHla(HighLevelAnalyzer):
4
search_for = StringSetting()
5
result_types = {
6
'mytype': {
7
'format': 'Output type: {{type}}, Input type: {{data.input_type}}'
8
}
9
}
10
11
def __init__(self):
12
pass
13
14
def decode(self, frame: AnalyzerFrame):
15
return AnalyzerFrame('mytype', frame.start_time, frame.end_time, {
16
'input_type': frame.type
17
})
Copied!
If you open the example data from above and add this analyzer, selecting the Async Serial analyzer as input, you should see the following when zooming in:
Our HLA (top) is outputting a frame for every frame from the input analyzer (bottom), and displaying their types.

Understanding the Input Frames

The goal is to search for a message within the input analyzer, but first we need to understand what frames the input analyzer (Async Serial in this case) produces so we can know what frames will be passed into the decode(frame: AnalyzerFrame) function.
The frame formats are documented under Analyzer Frame Types, where we can find Async Serial.
The Async Serial output is simple - it only outputs one frame type, data, with 3 fields: data , error, and address. The serial data we are looking at will not be configured to produce frames with the address field, so we can ignore that.
To recap, the decode(frame) function in our HLA will be called once for each frame from the Async Serial analyzer, where:
  • frame.type will always be data
  • frame.data['data'] will be a `bytes` object with the data for that frame
  • frame.data['error'] will be set if there was an error

Updating `decode()` to search for "H" or "l"

Now that we we understand the input data, let's update our HLA to search for the character "H".
1
def decode(self, frame: AnalyzerFrame):
2
# The `data` field only contains one byte
3
try:
4
ch = frame.data['data'].decode('ascii')
5
except:
6
# Not an ASCII character
7
return
8
9
# If ch is 'H' or 'l', output a frame
10
if ch in 'Hl':
11
return AnalyzerFrame('mytype', frame.start_time, frame.end_time, {
12
'input_type': frame.type
13
})
Copied!
After applying the changes, you can open the menu for your HLA and select Reload Source Files to reload your HLA:
You should now only see HLA frames where the Async Serial frame is an H or l:

Replace the hardcoded search with a setting

Now that we can search for characters, it would be much more flexible to allow the user to choose the characters to search for - this is where our search_for setting that we added earlier comes in.
1
class MyHla(HighLevelAnalyzer):
2
search_for = StringSetting()
Copied!
Instead of using the hardcoded 'Hl', let's replace that with the value of search_for:
In decode()
1
# If the character matches the one we are searching for, output a new frame
2
if ch in self.search_for:
3
return AnalyzerFrame('mytype', frame.start_time, frame.end_time, {
4
'input_type': frame.type
5
})
Copied!
Now if you can specify the characters to search for in your HLA settings:
Click Edit to show the settings
Set the "Search For" setting
Now only the values 'S' and 'H' have frames

Updating the display string

To update the display string shown in the analyzer bubbles, the format string in result_types variable will need to be updated. 'mytype' will also be updated to 'match' to better represent that the frame represents a matched character.
1
result_types = {
2
'match': {
3
'format': 'Found: {{data.char}}'
4
}
5
}
Copied!
And in decode(): we need to update the data in AnalyzerFrame to include 'char', and update the frame 'type' to 'match'.
1
# If the character matches the one we are searching for, output a new frame
2
if ch in self.search_for:
3
return AnalyzerFrame('match', frame.start_time, frame.end_time, {
4
'char': ch
5
})
Copied!
After reloading your HLA you should see the new display strings:
That's a lot more descriptive!

Using time

AnalyzerFrames include a start_time and end_time. These get passed as the second and third parameter of AnalyzerFrame, and can be used to control the time span of a frame. Let's use it to fill in the gaps between the matching frames.
Let's add a __init__() to initialize the 2 time variables we will use to track the span of time that doesn't have a match:
1
def __init__(self):
2
self.no_match_start_time = None
3
self.no_match_end_time = None
Copied!
And update decode() to track these variables:
1
# If the character matches the one we are searching for, output a new frame
2
if ch in self.search_for:
3
frames = []
4
5
# If we had a region of no matches, output a frame for it
6
if self.no_match_start_time is not None and self.no_match_end_time is not None:
7
frames.append(AnalyzerFrame(
8
'nomatch', self.no_match_start_time, self.no_match_end_time, {}))
9
10
# Reset match start/end variables
11
self.no_match_start_time = None
12
self.no_match_end_time = None
13
14
frames.append(AnalyzerFrame('match', frame.start_time, frame.end_time, {
15
'char': ch
16
}))
17
18
return frames
19
else:
20
# This frame doesn't match, so let's track when it began, and when it might end
21
if self.no_match_start_time is None:
22
self.no_match_start_time = frame.start_time
23
self.no_match_end_time = frame.end_time
Copied!
And lastly, add an entry in result_types for our new AnalyzerFrame type 'nomatch':
1
result_types = {
2
'match': {
3
'format': 'Match: {{data.char}}'
4
},
5
'nomatch': {
6
'format': 'No Match'
7
}
8
}
Copied!
The final output after reloading:

What's Next?

  • Find out about other analyzers and the AnalyzerFrames they output in the Analyzer Frame Types documentation.
  • Use the API Documentation as a reference.
  • Browse the Saleae Marketplace in Logic 2 for more ideas and examples of extensions you can create.
  • Publish your extension to the Saleae Marketplace!
Last modified 4mo ago