Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,14 @@ start docs\_build\index.html

## Running examples

1. First, run the PythonPanelService (not part of this repo, provided seperately)
2. Run the command `poetry run python examples/hello/hello.py`
3. Open http://localhost:42001/panel-service/panels/hello_panel/ in your browser
4. If there is an error about missing imports (especially nipanel), execute this
command (from the nipanel-python directory) to install the dependencies into the venv:
`%localappdata%\Temp\python_panel_service_venv\Scripts\python.exe -m pip install .\[examples,dev]`,
then restart the PythonPanelService and re-run hello.py.

You can see all running panels (and stop them) at: http://localhost:42001/panel-service/
1. Run the **PythonPanelService** (not part of this repo, provided seperately)
2. `poetry install --with examples` to get the dependencies needed for the examples
3. Run the command(s):
- `poetry run python examples/hello/hello.py`
- `poetry run python examples/all_types/all_types.py`
- `poetry run python examples/simple_graph/simple_graph.py`
- `poetry run python examples/nidaqmx/nidaqmx_continuous_analog_input.py` (requires real or simulated devices)
4. Open http://localhost:42001/panel-service/ in your browser, which will show all running panels

# Debugging on the streamlit side

Expand Down
2 changes: 1 addition & 1 deletion examples/all_types/all_types_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import nipanel

panel = nipanel.get_panel_accessor()

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

panel = nipanel.get_panel_accessor()
for name in all_types_with_values.keys():
col1, col2 = st.columns([0.4, 0.6])

Expand Down
3 changes: 2 additions & 1 deletion examples/hello/hello_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

import nipanel

panel = nipanel.get_panel_accessor()

st.set_page_config(page_title="Hello World Example", page_icon="📊", layout="wide")
st.title("Hello World Example")

panel = nipanel.get_panel_accessor()
st.write(panel.get_value("hello_string", ""))
7 changes: 5 additions & 2 deletions examples/nidaqmx/nidaqmx_continuous_analog_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
)
panel.set_value("sample_rate", task._timing.samp_clk_rate)
task.start()
print(f"Panel URL: {panel.panel_url}")
try:
print(f"\nPress Ctrl + C to stop")
print(f"Press Ctrl + C to stop")
while True:
data = task.read(number_of_samples_per_channel=1000)
data = task.read(
number_of_samples_per_channel=1000 # pyright: ignore[reportArgumentType]
)
panel.set_value("voltage_data", data[0])
panel.set_value("thermocouple_data", data[1])
except KeyboardInterrupt:
Expand Down
8 changes: 5 additions & 3 deletions examples/nidaqmx/nidaqmx_continuous_analog_input_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import nipanel

panel = nipanel.get_panel_accessor()

st.set_page_config(page_title="NI-DAQmx Example", page_icon="📈", layout="wide")
st.title("Analog Input - Voltage and Thermocouple in a Single Task")
voltage_tab, thermocouple_tab = st.tabs(["Voltage", "Thermocouple"])

Expand All @@ -21,13 +21,15 @@
unsafe_allow_html=True,
)

panel = nipanel.get_panel_accessor()
thermocouple_data = panel.get_value("thermocouple_data", [0.0])
voltage_data = panel.get_value("voltage_data", [0.0])

sample_rate = panel.get_value("sample_rate", 0)
sample_rate = panel.get_value("sample_rate", 0.0)

st.header("Voltage & Thermocouple")
voltage_therm_graph = {
"animation": False,
"tooltip": {"trigger": "axis"},
"legend": {"data": ["Voltage (V)", "Temperature (C)"]},
"xAxis": {
Expand Down Expand Up @@ -64,7 +66,7 @@
},
],
}
st_echarts(options=voltage_therm_graph, height="400px")
st_echarts(options=voltage_therm_graph, height="400px", key="voltage_therm_graph")

voltage_tab.header("Voltage")
with voltage_tab:
Expand Down
2 changes: 1 addition & 1 deletion examples/simple_graph/simple_graph_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import nipanel

panel = nipanel.get_panel_accessor()

st.set_page_config(page_title="Simple Graph Example", page_icon="📈", layout="wide")
st.title("Simple Graph Example")

panel = nipanel.get_panel_accessor()
time_points = panel.get_value("time_points", [0.0])
sine_values = panel.get_value("sine_values", [0.0])
amplitude = panel.get_value("amplitude", 1.0)
Expand Down
335 changes: 165 additions & 170 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions protos/ni/pythonpanel/v1/python_panel_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ message StartPanelRequest {

// Absolute path of the panel script's file on disk, or network path to the file
string panel_script_path = 2;

// Path to the python interpreter to use for the panel script.
string python_path = 3;
}

message StartPanelResponse {
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ protobuf = {version=">=4.21"}
ni-measurement-plugin-sdk = {version=">=2.3"}
typing-extensions = ">=4.13.2"
streamlit = ">=1.24"
streamlit-echarts = ">=0.4.0"
nitypes = {version=">=0.1.0dev2", allow-prereleases=true}
numpy = [
{version=">=1.20.0,<2.0.0", python=">=3.9,<3.10"},
{version=">=2.0.0", python=">=3.10"}
]
debugpy = ">=1.8.1"

[tool.poetry.group.dev.dependencies]
Expand Down
48 changes: 24 additions & 24 deletions src/ni/pythonpanel/v1/python_panel_service_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion src/ni/pythonpanel/v1/python_panel_service_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@ class StartPanelRequest(google.protobuf.message.Message):

PANEL_ID_FIELD_NUMBER: builtins.int
PANEL_SCRIPT_PATH_FIELD_NUMBER: builtins.int
PYTHON_PATH_FIELD_NUMBER: builtins.int
panel_id: builtins.str
"""Unique ID of the panel"""
panel_script_path: builtins.str
"""Absolute path of the panel script's file on disk, or network path to the file"""
python_path: builtins.str
"""Path to the python interpreter to use for the panel script."""
def __init__(
self,
*,
panel_id: builtins.str = ...,
panel_script_path: builtins.str = ...,
python_path: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["panel_id", b"panel_id", "panel_script_path", b"panel_script_path"]) -> None: ...
def ClearField(self, field_name: typing.Literal["panel_id", b"panel_id", "panel_script_path", b"panel_script_path", "python_path", b"python_path"]) -> None: ...

global___StartPanelRequest = StartPanelRequest

Expand Down
5 changes: 4 additions & 1 deletion src/nipanel/_panel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

import sys
from abc import ABC
from pathlib import Path

import grpc
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
Expand Down Expand Up @@ -35,7 +37,8 @@ def __init__(
grpc_channel=grpc_channel,
)
self._panel_script_path = panel_script_path
self._panel_url = self._panel_client.start_panel(panel_id, panel_script_path)
python_path = str(Path(sys.executable).resolve())
self._panel_url = self._panel_client.start_panel(panel_id, panel_script_path, python_path)

@property
def panel_script_path(self) -> str:
Expand Down
5 changes: 3 additions & 2 deletions src/nipanel/_panel_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,19 @@ def __init__(
self._grpc_channel = grpc_channel
self._stub: PythonPanelServiceStub | None = None

def start_panel(self, panel_id: str, panel_script_path: str) -> str:
def start_panel(self, panel_id: str, panel_script_path: str, python_path: str) -> str:
"""Start the panel.

Args:
panel_id: The ID of the panel to start.
panel_script_path: The path of the panel script file.
python_path: The path to the Python executable.

Returns:
The URL of the panel.
"""
start_panel_request = StartPanelRequest(
panel_id=panel_id, panel_script_path=panel_script_path
panel_id=panel_id, panel_script_path=panel_script_path, python_path=python_path
)
response = self._invoke_with_retry(self._get_stub().StartPanel, start_panel_request)
return response.panel_url
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/test_panel_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def test___enumerate_is_empty(fake_panel_channel: grpc.Channel) -> None:
def test___start_panels___enumerate_has_panels(fake_panel_channel: grpc.Channel) -> None:
client = create_panel_client(fake_panel_channel)

client.start_panel("panel1", "uri1")
client.start_panel("panel2", "uri2")
client.start_panel("panel1", "uri1", "python.exe")
client.start_panel("panel2", "uri2", "python.exe")

assert client.enumerate_panels() == {
"panel1": ("http://localhost:50051/panel1", []),
Expand All @@ -26,8 +26,8 @@ def test___start_panels___stop_panel_1_with_reset___enumerate_has_panel_2(
fake_panel_channel: grpc.Channel,
) -> None:
client = create_panel_client(fake_panel_channel)
client.start_panel("panel1", "uri1")
client.start_panel("panel2", "uri2")
client.start_panel("panel1", "uri1", "python.exe")
client.start_panel("panel2", "uri2", "python.exe")

client.stop_panel("panel1", reset=True)

Expand All @@ -40,8 +40,8 @@ def test___start_panels___stop_panel_1_without_reset___enumerate_has_both_panels
fake_panel_channel: grpc.Channel,
) -> None:
client = create_panel_client(fake_panel_channel)
client.start_panel("panel1", "uri1")
client.start_panel("panel2", "uri2")
client.start_panel("panel1", "uri1", "python.exe")
client.start_panel("panel2", "uri2", "python.exe")

client.stop_panel("panel1", reset=False)

Expand Down