Skip to content

Commit 97cdef0

Browse files
mikeprosserniMike Prosser
andauthored
Input improvements (#106)
* add numeric and enum inputs to the simple graph example * get_value returns the enum type when a default is provided * _sync_session_state() * GetValueResponse.found * nipanel.enum_selectbox * fix mypy * start improving daqmx example * Enhance enum support in Streamlit panel and tests - Added MyIntableFlags and MyIntableEnum classes to define new enum types. - Updated all_types_with_values to include new enum types. - Modified PanelValueAccessor to allow list values without type matching. - Improved test coverage for enum types in StreamlitPanel. * Refactor NI-DAQmx example to enhance channel settings and timing configuration in Streamlit panel * finish updating daqmx example, so all the controls (except I/O) are fully functional * clean up lint and mypy * update comment in proto file * revert simple_graph changes * put enum_selectbox in a controls folder * cosmetic improvements for the nidaqmx example panel * flag checkboxes control * refactor: reorganize controls imports and update usage in examples * cleanup * feat: implement set_value_if_changed method to optimize value updates and add tests * cleanup * cleanup * uptake latest proto file with optional value in GetValueResponse * misc feedback * type annotations * improve run/stop button behavior in nidaqmx example * brad's feedback --------- Co-authored-by: Mike Prosser <Mike.Prosser@emerson.com>
1 parent 2f3724a commit 97cdef0

14 files changed

+838
-118
lines changed
Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,40 @@
11
"""A Streamlit visualization panel for the all_types.py example script."""
22

3+
from enum import Enum, Flag
4+
35
import streamlit as st
46
from define_types import all_types_with_values
57

68
import nipanel
9+
from nipanel.controls import enum_selectbox, flag_checkboxes
710

811

912
st.set_page_config(page_title="All Types Example", page_icon="📊", layout="wide")
1013
st.title("All Types Example")
1114

1215
panel = nipanel.get_panel_accessor()
1316
for name in all_types_with_values.keys():
14-
col1, col2 = st.columns([0.4, 0.6])
17+
st.markdown("---")
18+
19+
default_value = all_types_with_values[name]
20+
col1, col2, col3 = st.columns([0.2, 0.2, 0.6])
1521

1622
with col1:
1723
st.write(name)
1824

1925
with col2:
20-
st.write(panel.get_value(name))
26+
if isinstance(default_value, bool):
27+
st.checkbox(label=name, value=default_value, key=name)
28+
elif isinstance(default_value, Flag):
29+
flag_checkboxes(panel, label=name, value=default_value, key=name)
30+
elif isinstance(default_value, Enum) and not isinstance(default_value, Flag):
31+
enum_selectbox(panel, label=name, value=default_value, key=name)
32+
elif isinstance(default_value, int) and not isinstance(default_value, Flag):
33+
st.number_input(label=name, value=default_value, key=name)
34+
elif isinstance(default_value, float):
35+
st.number_input(label=name, value=default_value, key=name, format="%.2f")
36+
elif isinstance(default_value, str):
37+
st.text_input(label=name, value=default_value, key=name)
38+
39+
with col3:
40+
st.write(panel.get_value(name, default_value=default_value))

examples/all_types/define_types.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ class MyIntFlags(enum.IntFlag):
1515
VALUE4 = 4
1616

1717

18+
class MyIntableFlags(enum.Flag):
19+
"""Example of an Flag enum with int values."""
20+
21+
VALUE8 = 8
22+
VALUE16 = 16
23+
VALUE32 = 32
24+
25+
1826
class MyIntEnum(enum.IntEnum):
1927
"""Example of an IntEnum enum."""
2028

@@ -23,6 +31,14 @@ class MyIntEnum(enum.IntEnum):
2331
VALUE30 = 30
2432

2533

34+
class MyIntableEnum(enum.Enum):
35+
"""Example of an enum with int values."""
36+
37+
VALUE100 = 100
38+
VALUE200 = 200
39+
VALUE300 = 300
40+
41+
2642
class MyStrEnum(str, enum.Enum):
2743
"""Example of a mixin string enum."""
2844

@@ -31,23 +47,46 @@ class MyStrEnum(str, enum.Enum):
3147
VALUE3 = "value3"
3248

3349

50+
class MyStringableEnum(enum.Enum):
51+
"""Example of an enum with string values."""
52+
53+
VALUE1 = "value1"
54+
VALUE2 = "value2"
55+
VALUE3 = "value3"
56+
57+
58+
class MyMixedEnum(enum.Enum):
59+
"""Example of an enum with mixed values."""
60+
61+
VALUE1 = "value1"
62+
VALUE2 = 2
63+
VALUE3 = 3.0
64+
65+
3466
all_types_with_values = {
3567
# supported scalar types
3668
"bool": True,
3769
"bytes": b"robotext",
3870
"float": 13.12,
3971
"int": 42,
4072
"str": "sample string",
73+
# supported enum and flag types
74+
"intflags": MyIntFlags.VALUE1 | MyIntFlags.VALUE4,
75+
"intenum": MyIntEnum.VALUE20,
76+
"strenum": MyStrEnum.VALUE3,
77+
"intableenum": MyIntableEnum.VALUE200,
78+
"intableflags": MyIntableFlags.VALUE8 | MyIntableFlags.VALUE32,
79+
"stringableenum": MyStringableEnum.VALUE2,
80+
"mixedenum": MyMixedEnum.VALUE2,
81+
# NI types
82+
"nitypes_Scalar": Scalar(42, "m"),
83+
"nitypes_AnalogWaveform": AnalogWaveform.from_array_1d(np.array([1.0, 2.0, 3.0])),
4184
# supported collection types
4285
"bool_collection": [True, False, True],
4386
"bytes_collection": [b"one", b"two", b"three"],
4487
"float_collection": [1.1, 2.2, 3.3],
4588
"int_collection": [1, 2, 3],
4689
"str_collection": ["one", "two", "three"],
47-
# supported enum and flag types
48-
"intflags": MyIntFlags.VALUE1 | MyIntFlags.VALUE4,
49-
"intenum": MyIntEnum.VALUE20,
50-
"strenum": MyStrEnum.VALUE3,
5190
"intflags_collection": [MyIntFlags.VALUE1, MyIntFlags.VALUE2, MyIntFlags.VALUE4],
5291
"intenum_collection": [MyIntEnum.VALUE10, MyIntEnum.VALUE20, MyIntEnum.VALUE30],
5392
"strenum_collection": [MyStrEnum.VALUE1, MyStrEnum.VALUE2, MyStrEnum.VALUE3],
@@ -56,9 +95,6 @@ class MyStrEnum(str, enum.Enum):
5695
"tuple": (4, 5, 6),
5796
"set": {7, 8, 9},
5897
"frozenset": frozenset([10, 11, 12]),
59-
# NI types
60-
"nitypes_Scalar": Scalar(42, "m"),
61-
"nitypes_AnalogWaveform": AnalogWaveform.from_array_1d(np.array([1.0, 2.0, 3.0])),
6298
# supported 2D collections
6399
"list_list_float": [[1.0, 2.0], [3.0, 4.0]],
64100
"tuple_tuple_float": ((1.0, 2.0), (3.0, 4.0)),
Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,83 @@
11
"""Data acquisition script that continuously acquires analog input data."""
22

3+
import time
34
from pathlib import Path
45

56
import nidaqmx
6-
from nidaqmx.constants import AcquisitionType
7+
from nidaqmx.constants import (
8+
AcquisitionType,
9+
TerminalConfiguration,
10+
CJCSource,
11+
TemperatureUnits,
12+
ThermocoupleType,
13+
LoggingMode,
14+
LoggingOperation,
15+
)
716

817
import nipanel
918

1019
panel_script_path = Path(__file__).with_name("nidaqmx_continuous_analog_input_panel.py")
1120
panel = nipanel.create_panel(panel_script_path)
1221

13-
# How to use nidaqmx: https://nidaqmx-python.readthedocs.io/en/stable/
14-
with nidaqmx.Task() as task:
15-
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
16-
task.ai_channels.add_ai_thrmcpl_chan("Dev1/ai1")
17-
task.timing.cfg_samp_clk_timing(
18-
rate=1000.0, sample_mode=AcquisitionType.CONTINUOUS, samps_per_chan=3000
19-
)
20-
panel.set_value("sample_rate", task._timing.samp_clk_rate)
21-
task.start()
22+
try:
2223
print(f"Panel URL: {panel.panel_url}")
23-
try:
24-
print(f"Press Ctrl + C to stop")
25-
while True:
26-
data = task.read(
27-
number_of_samples_per_channel=1000 # pyright: ignore[reportArgumentType]
24+
print(f"Waiting for the 'Run' button to be pressed...")
25+
print(f"(Press Ctrl + C to quit)")
26+
while True:
27+
panel.set_value("run_button", False)
28+
while not panel.get_value("run_button", False):
29+
time.sleep(0.1)
30+
31+
# How to use nidaqmx: https://nidaqmx-python.readthedocs.io/en/stable/
32+
with nidaqmx.Task() as task:
33+
task.ai_channels.add_ai_voltage_chan(
34+
physical_channel="Dev1/ai0",
35+
min_val=panel.get_value("voltage_min_value", -5.0),
36+
max_val=panel.get_value("voltage_max_value", 5.0),
37+
terminal_config=panel.get_value(
38+
"terminal_configuration", TerminalConfiguration.DEFAULT
39+
),
40+
)
41+
task.ai_channels.add_ai_thrmcpl_chan(
42+
"Dev1/ai1",
43+
min_val=panel.get_value("thermocouple_min_value", 0.0),
44+
max_val=panel.get_value("thermocouple_max_value", 100.0),
45+
units=panel.get_value("thermocouple_units", TemperatureUnits.DEG_C),
46+
thermocouple_type=panel.get_value("thermocouple_type", ThermocoupleType.K),
47+
cjc_source=panel.get_value(
48+
"thermocouple_cjc_source", CJCSource.CONSTANT_USER_VALUE
49+
),
50+
cjc_val=panel.get_value("thermocouple_cjc_val", 25.0),
51+
)
52+
task.timing.cfg_samp_clk_timing(
53+
rate=panel.get_value("sample_rate_input", 1000.0),
54+
sample_mode=AcquisitionType.CONTINUOUS,
55+
samps_per_chan=panel.get_value("samples_per_channel", 3000),
2856
)
29-
panel.set_value("voltage_data", data[0])
30-
panel.set_value("thermocouple_data", data[1])
31-
except KeyboardInterrupt:
32-
pass
33-
finally:
34-
task.stop()
57+
task.in_stream.configure_logging(
58+
file_path=panel.get_value("tdms_file_path", "data.tdms"),
59+
logging_mode=panel.get_value("logging_mode", LoggingMode.OFF),
60+
operation=LoggingOperation.OPEN_OR_CREATE,
61+
)
62+
panel.set_value("sample_rate", task._timing.samp_clk_rate)
63+
try:
64+
print(f"Starting data acquisition...")
65+
task.start()
66+
panel.set_value("is_running", True)
67+
68+
panel.set_value("stop_button", False)
69+
while not panel.get_value("stop_button", False):
70+
data = task.read(
71+
number_of_samples_per_channel=1000 # pyright: ignore[reportArgumentType]
72+
)
73+
panel.set_value("voltage_data", data[0])
74+
panel.set_value("thermocouple_data", data[1])
75+
except KeyboardInterrupt:
76+
raise
77+
finally:
78+
print(f"Stopping data acquisition...")
79+
task.stop()
80+
panel.set_value("is_running", False)
81+
82+
except KeyboardInterrupt:
83+
pass

0 commit comments

Comments
 (0)