Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Rpc support #1052

Merged
merged 37 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e7af7b5
WIP: Add simple rpc server
nstelter-slac Nov 6, 2023
21b6d76
WIP: add draft for simple rpc example
nstelter-slac Nov 7, 2023
a99cb9a
WIP: Add simple rpc client example
nstelter-slac Nov 14, 2023
4c85f40
WIP: Style updates to rpc example and example-server
nstelter-slac Nov 14, 2023
d9956bb
WIP: RPC support in working state
nstelter-slac Nov 27, 2023
b12a55b
ENH: Add RPC support
nstelter-slac Nov 28, 2023
6609f51
STY: Cleanup rpc example client/server and some p4p imports
nstelter-slac Nov 28, 2023
ad40187
TST: Add tst for parsing RPC channel info
nstelter-slac Nov 28, 2023
add82f0
TST: switch to checking if rpc address to use regex and add test for …
nstelter-slac Nov 30, 2023
f399ce2
TST: Add more tests for RPC support
nstelter-slac Nov 30, 2023
4512d7d
ENH: Add support for string args in RPC requests
nstelter-slac Nov 30, 2023
96bec84
BUG: Have rpc requesting polling only launch 1 thread
nstelter-slac Dec 1, 2023
9c7c59d
BUG: Make so rpc-polling only uses a single background thread
nstelter-slac Dec 6, 2023
3b600dd
DOC: Add RPC info to docs
nstelter-slac Dec 6, 2023
e669001
STY: minor update to RPC comments
nstelter-slac Dec 6, 2023
4c39ebc
BUG: Make RPC polling thread a daemon so closes when PyDM does
nstelter-slac Dec 6, 2023
5c2ad8d
BUG: Fix issue where RPC widgets have slow timeout when can't connect
nstelter-slac Dec 11, 2023
4b6ceac
STY: Add comment on why using daemon threads for RPC polling
nstelter-slac Dec 11, 2023
c50c1aa
STY: Add comments related to RPC server and client examples
nstelter-slac Dec 11, 2023
1515213
DOC: Exapand docs for RPC support
nstelter-slac Dec 11, 2023
145227b
DOC: remove reference to only working with PyDMLabel
nstelter-slac Dec 11, 2023
033bbcf
WIP: update rpc example client to use new NTURI wrapping method
nstelter-slac May 7, 2024
3fd873a
WIP: add example for rpc call no-args (doesn't work yet...)
nstelter-slac May 7, 2024
253c6b1
WIP: get previous RPC functionality (no-arg still not working) using …
nstelter-slac May 7, 2024
7754dbe
WIP: RPCs now work using wrapping method and with no-args
nstelter-slac May 8, 2024
ee4a30b
WIP: fix RPC example label to display floats
nstelter-slac May 8, 2024
05b0ce2
ENH: Add rpc support using NTURI wrapping method. Now supports RPC wi…
nstelter-slac May 8, 2024
cdc9367
STY: remove RPC related debug prints
nstelter-slac May 8, 2024
63d0a08
STY: add some more RPC related comments
nstelter-slac May 8, 2024
31fdc9d
STY: remove comments in RPC testing func
nstelter-slac May 8, 2024
9833bb2
BUG: fix rpc support for no-args+polling case
May 9, 2024
7fc5c58
BUG: fix more issues with rpc polling at correct rate
nstelter-slac May 9, 2024
1ca6259
DEV: remove rpc logging, since noisy and emitted signal already shows…
May 9, 2024
5ee56af
TST: fix rpc related tests
nstelter-slac May 9, 2024
c4d92bc
DOCS: add more docs for RPC
nstelter-slac May 14, 2024
c405978
DOC: remove duplicated RPC doc lines
nstelter-slac May 16, 2024
f86410e
STY: minor fixes from review
nstelter-slac May 23, 2024
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
33 changes: 33 additions & 0 deletions docs/source/data_plugins/p4p_plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,36 @@ Examples
A small pva testing ioc is included under ``examples/testing/pva_testing_ioc.py``. This can be run in order to
generate a couple of test PVs, which can be connected to using the example .ui file under
``examples/pva/pva.ui``.


RPC
---

The P4P data plugin also supports **remote method calls** (RPC) addresses.

RPC addresses allow for calling methods on a target IOC, and receiving back the method's result.
RPC addresses must contain arguments matching the name and data-type of those defined in the target's method.
These arguments are static and set in the widget's channel address.

RPCs can be set using a pva address in the following format::

pva://<address>?<arg_1_name>=<arg_1_value>&<arg_2_name>=<arg_2_value>&...(pydm_pollrate=<poll_rate_float>)

"pydm_pollrate" is an optional parameter, but when included must be placed after the arg name/value pairs in the address.
When "pydm_pollrate" is not used, the last arg name/value pair must still end with a "&" character.
(when not used, the RPC will be called once and not be polled)

Arguments are also optional. When not used, end the address with the "&" character (followed by the optional "pydm_pollrate")::

pva://<address>&(pydm_pollrate=<poll_rate_float>)

Example RPC addresses:

pva://my_address?arg1=value1&
pva://my_address?arg1=value1&arg2=value2&pydm_pollrate=10.5
pva://KLYS:LI12:11:ATTN_CUR&
pva://KLYS:LI12:11:ATTN_CUR&pydm_pollrate=2.0

Additional examples of using RPCs with PyDMLabels are provided in ``examples/rpc/rpc_labels.py``.
To run examples, first make sure ``python examples/testing_ioc/rpc_testing_ioc.py`` is actively
running in another terminal.
262 changes: 262 additions & 0 deletions examples/rpc/rpc_labels.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>562</width>
<height>502</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This label displays the result of a Remote Procedure Call (RPC), which gets back the result of simple function of the &amp;quot;target object&amp;quot; (connected channel)&lt;/p&gt;&lt;p&gt;The RPC channel specifies two int args and gets back the calculated result of adding them together. Arguments specified in RPC channels must have constant values.&lt;/p&gt;&lt;p&gt;The following 3 example PyDMLabels display the result from 3 different functions with 3 different RPC calls, showcasing use of differing number of args and arg types. (hold middle-click and hover over the result to see the RPC address used)&lt;/p&gt;&lt;p&gt;For this example to work properly, first have running in another terminal &amp;quot;python examples/testing_ioc/rpc_testing_ioc.py&amp;quot;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>(no polling) 2 + 7 = </string>
</property>
</widget>
</item>
<item>
<widget class="PyDMLabel" name="PyDMLabel">
<property name="toolTip">
<string/>
</property>
<property name="precision" stdset="0">
<number>0</number>
</property>
<property name="showUnits" stdset="0">
<bool>false</bool>
</property>
<property name="precisionFromPV" stdset="0">
<bool>true</bool>
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string>pva://pv:call:add_two_ints?a=2&amp;b=7&amp;</string>
</property>
<property name="enableRichText" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>(pollrate=0.2 sec) 2 + 7 (*-1 if negate) =</string>
</property>
</widget>
</item>
<item>
<widget class="PyDMLabel" name="PyDMLabel_2">
<property name="toolTip">
<string/>
</property>
<property name="precision" stdset="0">
<number>0</number>
</property>
<property name="showUnits" stdset="0">
<bool>false</bool>
</property>
<property name="precisionFromPV" stdset="0">
<bool>true</bool>
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string>pva://pv:call:add_three_ints_negate_option?a=2&amp;b=7&amp;negate=True&amp;pydm_pollrate=0.2</string>
</property>
<property name="enableRichText" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>(pollrate=1 sec) 3 + 7.8 =</string>
</property>
</widget>
</item>
<item>
<widget class="PyDMLabel" name="PyDMLabel_3">
<property name="toolTip">
<string/>
</property>
<property name="precision" stdset="0">
<number>2</number>
</property>
<property name="showUnits" stdset="0">
<bool>false</bool>
</property>
<property name="precisionFromPV" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string>pva://pv:call:add_int_float?a=3&amp;b=7.8&amp;pydm_pollrate=1.0</string>
</property>
<property name="enableRichText" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>(no polling) returns string &quot;Hello!!&quot;</string>
</property>
</widget>
</item>
<item>
<widget class="PyDMLabel" name="PyDMLabel_4">
<property name="toolTip">
<string/>
</property>
<property name="precision" stdset="0">
<number>0</number>
</property>
<property name="showUnits" stdset="0">
<bool>false</bool>
</property>
<property name="precisionFromPV" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string>pva://pv:call:take_return_string?a=Hello&amp;</string>
</property>
<property name="enableRichText" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>(pollrate=1 sec, no args) random float [0,10] = </string>
</property>
</widget>
</item>
<item>
<widget class="PyDMLabel" name="PyDMLabel_5">
<property name="toolTip">
<string/>
</property>
<property name="precision" stdset="0">
<number>2</number>
</property>
<property name="showUnits" stdset="0">
<bool>false</bool>
</property>
<property name="precisionFromPV" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string>pva://pv:call:no_args&amp;pydm_pollrate=1.0</string>
</property>
<property name="enableRichText" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PyDMLabel</class>
<extends>QLabel</extends>
<header>pydm.widgets.label</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
20 changes: 20 additions & 0 deletions examples/rpc/rpc_testing_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
This is an example of a simple client that sends RPCs.
To demo, first run 'python examples/testing_ioc/rpc_testing_ioc.py'
from another terminal,
then run this file with 'python rpc_testing_client.py'
"""

from p4p.client.thread import Context
from p4p.nt import NTURI

ctx = Context("pva")

# NTURI() lets us wrap argument into Value type needed in rpc call
# https://mdavidsaver.github.io/p4p/nt.html#p4p.nt.NTURI
AidaBPMSURI = NTURI([("a", "i"), ("b", "i")])

request = AidaBPMSURI.wrap("pv:call:add_two_ints", scheme="pva", kws={"a": 7, "b": 3})
response = ctx.rpc("pv:call:add_two_ints", request, timeout=10)

print(response) # should print something like 'Wed Dec 31 16:00:00 1969 10'
38 changes: 38 additions & 0 deletions examples/testing_ioc/rpc_testing_ioc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
This is an example of a server that sends back RPC results, mimicking the behavior of an ioc.
The server defines three functions with differing names, number of args, and arg types.
To view demo, first run this file with 'python rpc_testing_ioc.py',
and then run 'pydm examples/rpc/rpc_lables.ui' from another terminal.
(code adapted from p4p docs: https://mdavidsaver.github.io/p4p/rpc.html)
"""
from p4p.rpc import rpc, quickRPCServer
from p4p.nt import NTScalar
import random


class Demo(object):
@rpc(NTScalar("i"))
def add_two_ints(self, a, b):
return a + b

@rpc(NTScalar("f"))
def add_int_float(self, a, b):
return a + b

@rpc(NTScalar("i"))
def add_three_ints_negate_option(self, a, b, negate):
res = a + b
return -1 * res if negate else res

@rpc(NTScalar("s"))
def take_return_string(self, a):
return a + "!!"

@rpc(NTScalar("f"))
def no_args(self):
randomFloat = random.uniform(0, 10)
return randomFloat


adder = Demo()
quickRPCServer(provider="Example", prefix="pv:call:", target=adder)
Loading
Loading