diff --git a/examples/nidaqmx/nidaqmx_analog_input_filtering/README.md b/examples/nidaqmx/nidaqmx_analog_input_filtering/README.md
new file mode 100644
index 0000000..caf340c
--- /dev/null
+++ b/examples/nidaqmx/nidaqmx_analog_input_filtering/README.md
@@ -0,0 +1,23 @@
+Prerequisites
+===============
+Requires a Physical or Simulated Device. Refer to the [Getting Started Section](https://github.com/ni/nidaqmx-python/blob/master/README.rst) to learn how to create a simulated device.
+## Sample
+
+This is an nipanel example that displays an interactive Streamlit app and updates and fetches data from device.
+
+### Feature
+
+Script demonstrates analog input data getting continuously acquired, and being filtered.
+- Supports various data types
+
+### Required Software
+
+- Python 3.9 or later
+
+### Usage
+
+```pwsh
+poetry install --with examples
+poetry run python examples\nidaqmx\nidaqmx_analog_input_filtering\nidaqmx_analog_input_filtering.py
+```
+
diff --git a/examples/nidaqmx/nidaqmx_analog_input_filtering/nidaqmx_analog_input_filtering.py b/examples/nidaqmx/nidaqmx_analog_input_filtering/nidaqmx_analog_input_filtering.py
new file mode 100644
index 0000000..62b6511
--- /dev/null
+++ b/examples/nidaqmx/nidaqmx_analog_input_filtering/nidaqmx_analog_input_filtering.py
@@ -0,0 +1,154 @@
+"""Data acquisition script that continuously acquires analog input data."""
+
+import time
+from pathlib import Path
+
+import nidaqmx
+import nidaqmx.system
+from nidaqmx.constants import (
+ AcquisitionType,
+ CurrentShuntResistorLocation,
+ CurrentUnits,
+ Edge,
+ ExcitationSource,
+ FilterResponse,
+ Slope,
+ StrainGageBridgeType,
+ TerminalConfiguration,
+)
+from nidaqmx.errors import DaqError
+
+import nipanel
+
+panel_script_path = Path(__file__).with_name("nidaqmx_analog_input_filtering_panel.py")
+panel = nipanel.create_streamlit_panel(panel_script_path)
+panel.set_value("is_running", False)
+
+system = nidaqmx.system.System.local()
+
+available_channel_names = []
+for dev in system.devices:
+ for chan in dev.ai_physical_chans:
+ available_channel_names.append(chan.name)
+panel.set_value("available_channel_names", available_channel_names)
+
+available_trigger_sources = [""]
+for dev in system.devices:
+ if hasattr(dev, "terminals"):
+ for term in dev.terminals:
+ available_trigger_sources.append(term)
+panel.set_value("available_trigger_sources", available_trigger_sources)
+try:
+ panel.set_value("daq_error", "")
+ print(f"Panel URL: {panel.panel_url}")
+ print(f"Waiting for the 'Run' button to be pressed...")
+ print(f"(Press Ctrl + C to quit)")
+ while True:
+ panel.set_value("run_button", False)
+ while not panel.get_value("run_button", False):
+ time.sleep(0.1)
+ # How to use nidaqmx: https://nidaqmx-python.readthedocs.io/en/stable/
+ with nidaqmx.Task() as task:
+
+ chan_type = panel.get_value("chan_type", "1")
+
+ if chan_type == "2":
+ chan = task.ai_channels.add_ai_current_chan(
+ panel.get_value("physical_channel", ""),
+ max_val=panel.get_value("max_value_current", 0.01),
+ min_val=panel.get_value("min_value_current", -0.01),
+ ext_shunt_resistor_val=panel.get_value("shunt_resistor_value", 249.0),
+ shunt_resistor_loc=panel.get_value(
+ "shunt_location", CurrentShuntResistorLocation.EXTERNAL
+ ),
+ units=panel.get_value("units", CurrentUnits.AMPS),
+ )
+
+ elif chan_type == "3":
+ chan = task.ai_channels.add_ai_strain_gage_chan(
+ panel.get_value("physical_channel", ""),
+ nominal_gage_resistance=panel.get_value("gage_resistance", 350.0),
+ voltage_excit_source=ExcitationSource.EXTERNAL, # Only mode that works
+ max_val=panel.get_value("max_value_strain", 0.001),
+ min_val=panel.get_value("min_value_strain", -0.001),
+ poisson_ratio=panel.get_value("poisson_ratio", 0.3),
+ lead_wire_resistance=panel.get_value("wire_resistance", 0.0),
+ initial_bridge_voltage=panel.get_value("initial_voltage", 0.0),
+ gage_factor=panel.get_value("gage_factor", 2.0),
+ voltage_excit_val=panel.get_value("voltage_excitation_value", 0.0),
+ strain_config=panel.get_value(
+ "strain_configuration", StrainGageBridgeType.FULL_BRIDGE_I
+ ),
+ )
+ else:
+ chan = task.ai_channels.add_ai_voltage_chan(
+ panel.get_value("physical_channel", ""),
+ terminal_config=panel.get_value(
+ "terminal_configuration", TerminalConfiguration.DEFAULT
+ ),
+ max_val=panel.get_value("max_value_voltage", 5.0),
+ min_val=panel.get_value("min_value_voltage", -5.0),
+ )
+ task.timing.cfg_samp_clk_timing(
+ source=panel.get_value("source", ""), # "" - means Onboard Clock (default value)
+ rate=panel.get_value("rate", 1000.0),
+ sample_mode=AcquisitionType.CONTINUOUS,
+ samps_per_chan=panel.get_value("total_samples", 100),
+ )
+ panel.set_value("sample_rate", task.timing.samp_clk_rate)
+ # Not all hardware supports all filter types.
+ # Refer to your device documentation for more information.
+ if panel.get_value("filter", "Filter") == "Filter":
+ chan.ai_filter_enable = True
+ chan.ai_filter_freq = panel.get_value("filter_freq", 0.0)
+ chan.ai_filter_response = panel.get_value("filter_response", FilterResponse.COMB)
+ chan.ai_filter_order = panel.get_value("filter_order", 1)
+ panel.set_value("actual_filter_freq", chan.ai_filter_freq)
+ panel.set_value("actual_filter_response", chan.ai_filter_response)
+ panel.set_value("actual_filter_order", chan.ai_filter_order)
+ else:
+ panel.set_value("actual_filter_freq", 0.0)
+ panel.set_value("actual_filter_response", FilterResponse.COMB)
+ panel.set_value("actual_filter_order", 0)
+ # Not all hardware supports all filter types.
+ # Refer to your device documentation for more information.
+ trigger_type = panel.get_value("trigger_type")
+ if trigger_type == "5":
+ task.triggers.start_trigger.cfg_anlg_edge_start_trig(
+ trigger_source=panel.get_value("analog_source", ""),
+ trigger_slope=panel.get_value("slope", Slope.FALLING),
+ trigger_level=panel.get_value("level", 0.0),
+ )
+
+ if trigger_type == "2":
+ task.triggers.start_trigger.cfg_dig_edge_start_trig(
+ trigger_source=panel.get_value("digital_source", ""),
+ trigger_edge=panel.get_value("edge", Edge.FALLING),
+ )
+ task.triggers.start_trigger.anlg_edge_hyst = hysteresis = panel.get_value(
+ "hysteresis", 0.0
+ )
+
+ try:
+ task.start()
+ panel.set_value("is_running", True)
+
+ panel.set_value("stop_button", False)
+ while not panel.get_value("stop_button", False):
+ data = task.read(
+ number_of_samples_per_channel=100 # pyright: ignore[reportArgumentType]
+ )
+ panel.set_value("acquired_data", data)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ task.stop()
+ panel.set_value("is_running", False)
+
+except DaqError as e:
+ daq_error = str(e)
+ print(daq_error)
+ panel.set_value("daq_error", daq_error)
+
+except KeyboardInterrupt:
+ pass
diff --git a/examples/nidaqmx/nidaqmx_analog_input_filtering/nidaqmx_analog_input_filtering_panel.py b/examples/nidaqmx/nidaqmx_analog_input_filtering/nidaqmx_analog_input_filtering_panel.py
new file mode 100644
index 0000000..fb16937
--- /dev/null
+++ b/examples/nidaqmx/nidaqmx_analog_input_filtering/nidaqmx_analog_input_filtering_panel.py
@@ -0,0 +1,408 @@
+"""Streamlit visualization script to display data acquired by nidaqmx_analog_input_filtering.py."""
+
+import extra_streamlit_components as stx # type: ignore[import-untyped]
+import streamlit as st
+from nidaqmx.constants import (
+ CurrentShuntResistorLocation,
+ CurrentUnits,
+ Edge,
+ FilterResponse,
+ Slope,
+ StrainGageBridgeType,
+ TerminalConfiguration,
+)
+from streamlit_echarts import st_echarts
+
+import nipanel
+from nipanel.controls import enum_selectbox
+
+st.set_page_config(page_title="Analog Input Filtering", page_icon="📈", layout="wide")
+st.title("Analog Input - Filtering")
+panel = nipanel.get_streamlit_panel_accessor()
+
+left_col, right_col = st.columns(2)
+
+
+st.markdown(
+ """
+
+
+ """,
+ unsafe_allow_html=True,
+)
+
+
+with left_col:
+ with st.container(border=True):
+ with st.container(border=True):
+ if panel.get_value("is_running", True):
+ st.button("Stop", key="stop_button")
+ elif not panel.get_value("is_running", True) and panel.get_value("daq_error", "") == "":
+ run_button = st.button("Run", key="run_button")
+ else:
+ st.error(
+ f"There was an error running the script. Fix the issue and re-run nidaqmx_analog_input_filtering.py \n\n {panel.get_value('daq_error', '')}"
+ )
+ st.title("Channel Settings")
+ physical_channel = st.selectbox(
+ options=panel.get_value("available_channel_names", ["Mod2/ai0"]),
+ index=0,
+ label="Physical Channels",
+ disabled=panel.get_value("is_running", False),
+ )
+ panel.set_value("physical_channel", physical_channel)
+ enum_selectbox(
+ panel,
+ label="Terminal Configuration",
+ value=TerminalConfiguration.DEFAULT,
+ disabled=panel.get_value("is_running", False),
+ key="terminal_configuration",
+ )
+
+ st.title("Timing Settings")
+
+ source = st.selectbox(
+ "Sample Clock Source",
+ options=panel.get_value("available_trigger_sources", [""]),
+ index=0,
+ disabled=panel.get_value("is_running", False),
+ )
+ panel.set_value("source", source)
+ st.number_input(
+ "Sample Rate",
+ value=1000.0,
+ min_value=1.0,
+ step=1.0,
+ disabled=panel.get_value("is_running", False),
+ key="rate",
+ )
+ st.number_input(
+ "Number of Samples",
+ value=100,
+ min_value=1,
+ step=1,
+ disabled=panel.get_value("is_running", False),
+ key="total_samples",
+ )
+ st.number_input(
+ "Actual Sample Rate",
+ value=panel.get_value("sample_rate", 1000.0),
+ key="actual_sample_rate",
+ step=1.0,
+ disabled=True,
+ )
+ st.title("Filtering Settings")
+
+ filter = st.selectbox(
+ "Filter",
+ options=["No Filtering", "Filter"],
+ disabled=panel.get_value("is_running", False),
+ )
+ panel.set_value("filter", filter)
+ enum_selectbox(
+ panel,
+ label="Filter Response",
+ value=FilterResponse.COMB,
+ disabled=panel.get_value("is_running", False),
+ key="filter_response",
+ )
+
+ filter_freq = st.number_input(
+ "Filtering Frequency",
+ value=1000.0,
+ step=1.0,
+ disabled=panel.get_value("is_running", False),
+ )
+ filter_order = st.number_input(
+ "Filter Order",
+ min_value=0,
+ max_value=1,
+ value=1,
+ disabled=panel.get_value("is_running", False),
+ )
+ st.selectbox(
+ "Actual Filter Frequency",
+ options=[panel.get_value("actual_filter_freq", 0.0)],
+ disabled=True,
+ )
+ st.selectbox(
+ "Actual Filter Order",
+ options=[panel.get_value("actual_filter_order", 0)],
+ disabled=True,
+ )
+
+with right_col:
+
+ with st.container(border=True):
+ st.title("Acquired Data")
+ acquired_data = panel.get_value("acquired_data", [0.0])
+ sample_rate = panel.get_value("sample_rate", 100.0)
+ acquired_data_graph = {
+ "animation": False,
+ "tooltip": {"trigger": "axis"},
+ "legend": {"data": ["Voltage (V)"]},
+ "xAxis": {
+ "type": "category",
+ "data": [x / sample_rate for x in range(len(acquired_data))],
+ "name": "Time",
+ "nameLocation": "center",
+ "nameGap": 40,
+ },
+ "yAxis": {
+ "type": "value",
+ "name": "Volts",
+ "nameRotate": 90,
+ "nameLocation": "center",
+ "nameGap": 40,
+ },
+ "series": [
+ {
+ "name": "voltage_amplitude",
+ "type": "line",
+ "data": acquired_data,
+ "emphasis": {"focus": "series"},
+ "smooth": True,
+ "seriesLayoutBy": "row",
+ },
+ ],
+ }
+ st_echarts(options=acquired_data_graph, height="400px", key="graph", width="100%")
+
+ st.title("Trigger Settings")
+ trigger_type = stx.tab_bar(
+ data=[
+ stx.TabBarItemData(id=1, title="No Trigger", description=""),
+ stx.TabBarItemData(id=2, title="Digital Start", description=""),
+ stx.TabBarItemData(id=3, title="Digital Pause", description=""),
+ stx.TabBarItemData(id=4, title="Digital Reference", description=""),
+ stx.TabBarItemData(id=5, title="Analog Start", description=""),
+ stx.TabBarItemData(id=6, title="Analog Pause", description=""),
+ stx.TabBarItemData(id=7, title="Analog Reference", description=""),
+ ],
+ default=1,
+ )
+ panel.set_value("trigger_type", trigger_type)
+
+ if trigger_type == "1":
+ with st.container(border=True):
+ st.write(
+ "To enable triggers, select a tab above, and configure the settings. \n Not all hardware supports all trigger types. Refer to your device documentation for more information."
+ )
+ if trigger_type == "2":
+ with st.container(border=True):
+ source = st.selectbox(
+ "Source", options=panel.get_value("available_trigger_sources", [""])
+ )
+ panel.set_value("digital_source", source)
+ enum_selectbox(
+ panel,
+ label="Edge",
+ value=Edge.FALLING,
+ disabled=panel.get_value("is_running", False),
+ key="edge",
+ )
+ if trigger_type == "3":
+ with st.container(border=True):
+ st.write(
+ "This trigger type is not supported in continuous sample timing. Refer to your device documentation for more information on which triggers are supported"
+ )
+ if trigger_type == "4":
+ with st.container(border=True):
+ st.write(
+ "This trigger type is not supported in continuous sample timing. Refer to your device documentation for more information on which triggers are supported"
+ )
+ if trigger_type == "5":
+ with st.container(border=True):
+ analog_source = st.text_input("Source", "APFI0", key="analog_source")
+ enum_selectbox(
+ panel,
+ label="Slope",
+ value=Slope.FALLING,
+ disabled=panel.get_value("is_running", False),
+ key="slope",
+ )
+
+ level = st.number_input("Level", key="level")
+ hysteriesis = st.number_input(
+ "Hysteriesis",
+ disabled=panel.get_value("is_running", False),
+ key="hysteriesis",
+ )
+
+ if trigger_type == "6":
+ with st.container(border=True):
+ st.write(
+ "This trigger type is not supported in continuous sample timing. Refer to your device documentation for more information on which triggers are supported"
+ )
+ if trigger_type == "7":
+ with st.container(border=True):
+ st.write(
+ "This trigger type is not supported in continuous sample timing. Refer to your device documentation for more information on which triggers are supported."
+ )
+ st.title("Task Types")
+
+ chan_type = stx.tab_bar(
+ data=[
+ stx.TabBarItemData(id=1, title="Voltage", description=""),
+ stx.TabBarItemData(id=2, title="Current", description=""),
+ stx.TabBarItemData(id=3, title="Strain Gage", description=""),
+ ],
+ default=1,
+ )
+
+ panel.set_value("chan_type", chan_type)
+ if chan_type == "1":
+ with st.container(border=True):
+ st.title("Voltage Data")
+ channel_left, channel_right = st.columns(2)
+ with channel_left:
+ max_value_voltage = st.number_input(
+ "Max Value",
+ value=5.0,
+ step=0.1,
+ disabled=panel.get_value("is_running", False),
+ key="max_value_voltage",
+ )
+
+ min_value_voltage = st.number_input(
+ "Min Value",
+ value=-5.0,
+ step=0.1,
+ disabled=panel.get_value("is_running", False),
+ key="min_value_voltage",
+ )
+
+ if chan_type == "2":
+ with st.container(border=True):
+ st.title("Current Data")
+ channel_left, channel_right = st.columns(2)
+ with channel_left:
+ enum_selectbox(
+ panel,
+ label="Shunt Resistor Location",
+ value=CurrentShuntResistorLocation.EXTERNAL,
+ disabled=panel.get_value("is_running", False),
+ key="shunt_location",
+ )
+ enum_selectbox(
+ panel,
+ label="Units",
+ value=CurrentUnits.AMPS,
+ disabled=panel.get_value("is_running", False),
+ key="units",
+ )
+ min_value_current = st.number_input(
+ "Min Value",
+ value=-0.01,
+ step=0.001,
+ disabled=panel.get_value("is_running", False),
+ )
+ max_value_current = st.number_input(
+ "Max Value",
+ value=0.01,
+ step=1.0,
+ key="max_value_current",
+ disabled=panel.get_value("is_running", False),
+ )
+ shunt_resistor_value = st.number_input(
+ "Shunt Resistor Value",
+ value=249.0,
+ step=1.0,
+ disabled=panel.get_value("is_running", False),
+ )
+ if chan_type == "3":
+ with st.container(border=True):
+ st.title("Strain Gage Data")
+ channel_left, channel_right = st.columns(2)
+ with channel_left:
+ min_value_strain = st.number_input(
+ "Min Value",
+ value=-0.01,
+ step=0.01,
+ key="min_value_strain",
+ )
+ max_value_strain = st.number_input(
+ "Max Value",
+ value=0.01,
+ step=0.01,
+ max_value=2.0,
+ key="max_value_strain",
+ )
+ enum_selectbox(
+ panel,
+ label="Strain Units",
+ value=CurrentUnits.AMPS,
+ disabled=panel.get_value("is_running", False),
+ key="strain_units",
+ )
+ st.title("Strain Gage Information")
+ gage_factor = st.number_input(
+ "Gage Factor",
+ value=2.0,
+ step=1.0,
+ disabled=panel.get_value("is_running", False),
+ key="gage_factor",
+ )
+ nominal_gage = st.number_input(
+ "Nominal Gage Resistance",
+ value=350.0,
+ step=1.0,
+ disabled=panel.get_value("is_running", False),
+ key="gage_resistance",
+ )
+ poisson_ratio = st.number_input(
+ "Poisson Ratio",
+ value=0.3,
+ step=1.0,
+ disabled=panel.get_value("is_running", False),
+ key="poisson_ratio",
+ )
+ st.title("Bridge Information")
+ enum_selectbox(
+ panel,
+ label="Strain Configuration",
+ value=StrainGageBridgeType.FULL_BRIDGE_I,
+ disabled=panel.get_value("is_running", False),
+ key="strain_configuration",
+ )
+ wire_resistance = st.number_input(
+ "Lead Wire Resistance",
+ value=0.0,
+ step=1.0,
+ key="wire_resistance",
+ )
+ initial_voltage = st.number_input(
+ "Initial Bridge Voltage",
+ value=0.0,
+ step=1.0,
+ disabled=panel.get_value("is_running", False),
+ key="initial_voltage",
+ )
+
+ st.selectbox(
+ label="Voltage Excitation Source",
+ key="voltage_excit",
+ options=["External"],
+ disabled=True,
+ )
+ panel.set_value("voltage_excitation_source", "voltage_excit")
+ voltage_excit = st.number_input(
+ "Voltage Excitation Value",
+ value=2.5,
+ step=1.0,
+ key="voltage_excitation_value",
+ disabled=panel.get_value("is_running", False),
+ )
diff --git a/examples/nidaqmx/README.md b/examples/nidaqmx/nidaqmx_continuous_analog_input/README.md
similarity index 100%
rename from examples/nidaqmx/README.md
rename to examples/nidaqmx/nidaqmx_continuous_analog_input/README.md
diff --git a/examples/nidaqmx/nidaqmx_continuous_analog_input.py b/examples/nidaqmx/nidaqmx_continuous_analog_input/nidaqmx_continuous_analog_input.py
similarity index 97%
rename from examples/nidaqmx/nidaqmx_continuous_analog_input.py
rename to examples/nidaqmx/nidaqmx_continuous_analog_input/nidaqmx_continuous_analog_input.py
index f8210a8..ec089ad 100644
--- a/examples/nidaqmx/nidaqmx_continuous_analog_input.py
+++ b/examples/nidaqmx/nidaqmx_continuous_analog_input/nidaqmx_continuous_analog_input.py
@@ -59,7 +59,7 @@
logging_mode=panel.get_value("logging_mode", LoggingMode.OFF),
operation=LoggingOperation.OPEN_OR_CREATE,
)
- panel.set_value("sample_rate", task._timing.samp_clk_rate)
+ panel.set_value("sample_rate", task.timing.samp_clk_rate)
try:
print(f"Starting data acquisition...")
task.start()
diff --git a/examples/nidaqmx/nidaqmx_continuous_analog_input_panel.py b/examples/nidaqmx/nidaqmx_continuous_analog_input/nidaqmx_continuous_analog_input_panel.py
similarity index 100%
rename from examples/nidaqmx/nidaqmx_continuous_analog_input_panel.py
rename to examples/nidaqmx/nidaqmx_continuous_analog_input/nidaqmx_continuous_analog_input_panel.py
diff --git a/poetry.lock b/poetry.lock
index 7143409..74a657d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -502,6 +502,20 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
[package.extras]
test = ["pytest (>=6)"]
+[[package]]
+name = "extra-streamlit-components"
+version = "0.1.80"
+description = "An all-in-one place, to find complex or just natively unavailable components on streamlit."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "extra_streamlit_components-0.1.80-py3-none-any.whl", hash = "sha256:7a8c151da5dcd1f1f97b6c29caa812a1d77928d20fc4bf42a3a4fd788274dd9e"},
+ {file = "extra_streamlit_components-0.1.80.tar.gz", hash = "sha256:87d6c38e07381501d8882796adef17d1c1d4e3a79615864d95c1163d2bc136f6"},
+]
+
+[package.dependencies]
+streamlit = ">=1.40.1"
+
[[package]]
name = "flake8"
version = "5.0.4"
@@ -3263,4 +3277,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<4.0,!=3.9.7"
-content-hash = "45dc36d0ef51734fbde48be7f49ed705209f3b0d46c827cd0de4115fbf15ce62"
+content-hash = "1b4c4105e78ce7eca099f1a45a130c93565d568f2263e70d6389ab59ed3bbe27"
diff --git a/pyproject.toml b/pyproject.toml
index 199b079..41393ee 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -64,6 +64,7 @@ optional = true
[tool.poetry.group.examples.dependencies]
streamlit-echarts = ">=0.4.0"
+extra-streamlit-components = "^0.1.80"
nidaqmx = { version = ">=0.8.0", allow-prereleases = true }
niscope = "^1.4.9"