epics-base / pvapy Goto Github PK
View Code? Open in Web Editor NEWpvaPy provides Python bindings for EPICS pvAccess
Home Page: https://epics.anl.gov/extensions/pvaPy/production/index.html
License: Other
pvaPy provides Python bindings for EPICS pvAccess
Home Page: https://epics.anl.gov/extensions/pvaPy/production/index.html
License: Other
--------------------------------------------------------- EPICS Base - the central core of a control system toolkit --------------------------------------------------------- Copyright UChicago Argonne LLC, as Operator of Argonne National Laboratory. Copyright (c) 1991-2003 The Regents of the University of California, as Operator of Los Alamos National Laboratory. EPICS Base is distributed subject to a Software License Agreement found in the file LICENSE that is included with this distribution. --------------------------------------------------------- Installation and release information can be found in the various files in the documentation subdirectory. Additional information about EPICS including mailing list archives and subscription instructions, documentation and training materials, additional components, links to other websites etc. is available on the EPICS home page at https://epics.anl.gov/ $Format:%cD$ $Format:%H$ https://code.launchpad.net/epics-base
I wondering if it is possible to add a custom field to an NTSCALAR record and still be considered an NTSCALAR record.
I need that for the EPICS archiver appliance because it would archive a PVACCESS record only if it is NTSCALAR, but in my case, I need to add custom fields.
Hi there, I've been using pvapy to set up my own PV server and update the values of the timestamps and PVs through the PvObject set method as a message arrives from another system. So far this works very well and the timestamps update as expected.
However, I would also like to be able to control the value of PVs through the standard pvput command line tool and I've found that I can do this fine to set the value, but the command doesn't update the timestamp, instead it uses the timestamp declared previously, e.g.
test 1990-01-01 00:00:00.456 -1 0
test 1990-01-01 00:00:00.456 -1 5
I've tried fixing this by setting the timestamp manually using the server.update() method within an onWriteCallback assigned to the PV when it is added to the server (see the code below). What this appears to do is update the value, and then immediately after it updates the timestamp, so when I monitor the channel I receive two updates for every pvput, like this:
test 1990-01-01 00:00:00.456 -1 0
test 1990-01-01 00:00:00.456 -1 5
test 2021-08-13 15:09:29.659 -1 5
test 2021-08-13 15:09:29.659 -1 6
test 2021-08-13 15:09:38.949 -1 6
test 2021-08-13 15:09:38.949 -1 7
test 2021-08-13 15:09:58.518 -1 7
In order to apply some extra formatting to the values from the other system I am using an object that inherits from the PvObject class. I can see that in the onWriteCallback a standard PvObject is passed as the parameter which is why I have to call set on my inherited PvObjects within the callback. I understand this would generate a new update (as a new set method is called), but I'm not sure how to avoid this if I want to manually update the timestamp.
Is it essential that the object is an ordinary PvObject for the server.update method to work, or have I misunderstood how the pvput command relates to the PvaServer and the onWriteCallback, and that actually it isn't supposed to update the timestamp anyway?
I've included some code below that is a representation of how I am currently using the onWriteCallback and inherited classes to help illustrate what I mean. Running this with pvput produces the pvmonitor output shown above.
from pvapy import PvaServer, INT, PvObject, STRING, PvTimeStamp
import time
def extract_seconds_and_nanoseconds_from_timestamp(timestamp):
seconds = int(timestamp // 1)
nanoseconds = int((timestamp % 1) * 1e9)
return seconds, nanoseconds
class ServerManager():
def __init__(self):
self.pvaserver = PvaServer()
self.pv = None
def create_pv_and_add_to_server(self, name):
timestamp = PvTimeStamp(123, 456000000)
pv = InheritedPvObject(
{'channelname': STRING, 'value': INT, 'timeStamp': timestamp},
{'channelname': name, 'timeStamp': timestamp.toDict()}
)
self.pv = pv
self.pvaserver.addRecord(name, self.pv.copy(), self.echo)
def echo(self, pv_object):
# extract the information from the pvobject that comes through (different type to
# the inherited one so we can't assign it straight off)
pv_object_fields = pv_object.get()
# update the timestamp
seconds, nanoseconds = extract_seconds_and_nanoseconds_from_timestamp(
time.time())
timestamp = PvTimeStamp(seconds, nanoseconds)
pv_object_fields['timeStamp'] = timestamp.toDict()
# set the new value in the Inherited object
self.pv.set(pv_object_fields)
# update the inherited object on the server with the new information
channel_name = pv_object_fields.get('channelname')
self.pvaserver.update(
channel_name, self.pv)
class InheritedPvObject(PvObject):
def __init__(self, type_dict, value_dict):
PvObject.__init__(self, type_dict, value_dict)
if __name__ == '__main__':
try:
server = ServerManager()
server.create_pv_and_add_to_server('test')
while True:
time.sleep(1)
except KeyboardInterrupt:
print('stopping server')
finally:
server.pvaserver.stop()
I am currently working in Python 3.6.8 with pvapy (3.1.0) on CentOS Linux release 8.4.2105, alongside EPICS version R7.0.5
Any guidance as to why this issue may be arising would be a great help. Thank you.
Trying to build pvaPy on a 64 bit Debian jessie/testing I get
ralph@debian-testing:~/work/EPICS/V4/pvaPy$ export EPICS_BASE=/usr/lib/epics
ralph@debian-testing:~/work/EPICS/V4/pvaPy$ make configure
cd tools/autoconf && autoreconf --install
configure.ac:5: installing './compile'
configure.ac:2: installing './install-sh'
configure.ac:2: installing './missing'
cd tools/autoconf && ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for style of include used by make... GNU
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking whether gcc understands -c and -o together... yes
checking dependency style of gcc... none
checking for python... /usr/bin/python
checking for a version of Python >= '2.1.0'... yes
checking for a version of Python >=2.6... yes
checking for the distutils Python package... yes
checking for Python include path... -I/usr/include/python2.7
checking for Python library path... -L/usr/lib -lpython2.7
checking for Python site-packages path... /usr/lib/python2.7/dist-packages
checking python extra libraries... -lpthread -ldl -lutil
checking python extra linking flags... -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions
checking consistency of all components of python development environment... yes
checking for g++... g++
checking whether we are using the GNU C++ compiler... yes
checking whether g++ accepts -g... yes
checking dependency style of g++... none
./configure: line 4478: syntax error near unexpected token `fi'
./configure: line 4478: `fi'
Makefile:31: recipe for target 'configure' failed
make: *** [configure] Error 2
The Normative Types spec says the order of fields matters and must be preserved. This is necessary for NTTable, but (looking at the pvaPy documentation) the column spec gets passed into the PVObject structure using a regular python dictionary, which AIUI doesn't preserve that order. Is this reasoning correct, or is there something else that we haven't understood?
From the discussion in epics-base/pvAccessCPP#60 I've looked for code using PVA.
@msekoranja states that a Requester should never hold a strong reference (shared_ptr) to the associated Operation. Doing so creates a reference loop which must be explicitly broken as the Operation holds a strong ref. to its Requester. For example, in src/pvaccess/ChannelGetRequesterImpl.h ChannelGetRequesterImpl::channelGet
. I haven't look any further than the header, so I don't know if this particular instance is causing a problem. So FYI...
Hi,
Not sure if this is an issue, but I would like to know how to suppress the std out from the C++ pvAccess library which shows everytime you start up a PvaServer:
pvAccess Server v7.1.2
Active configuration (w/ defaults)
EPICS_PVAS_INTF_ADDR_LIST = 0.0.0.0:5075
EPICS_PVAS_BEACON_ADDR_LIST =
EPICS_PVAS_AUTO_BEACON_ADDR_LIST = YES
EPICS_PVAS_BEACON_PERIOD = 15
EPICS_PVAS_BROADCAST_PORT = 5076
EPICS_PVAS_SERVER_PORT = 5075
EPICS_PVAS_PROVIDER_NAMES = local
I can't find any option in the documentation, but maybe I've been looking in the wrong place.
Any help on this would be greatly appreciated.
Hello, I've written a small Python module https://github.com/ajgdls/EPICSPyClientPerformance to compare the performance of 6 Python based EPICS clients. I have attempted to test each in a consistent way, and the README has some preliminary results for monitoring 1000 records at 10Hz for 1000 samples.
I would really appreciate it if this module owner would review EPICSPyClientPerformance test code for this client implementation to check that I'm actually testing in the correct and most efficient way. I would be happy to make updates that shows an increase in performance for this client.
This testing is driven by Diamond Light Source, and on 1st December the tests will be re-run against latest PyPI versions and then published (probably tech-talk) for further discussion, so if anyone wants to push performance optimisations they will get picked up on that date.
Thanks for taking a look, looking forward to hearing from everyone.
EPICS_BASE = /Users/arkilic/epics/base-3.14.12.4
⚙ arkilic@gaia ~/EPICS-CPP-4.5.0-rc1/pvaPy master make
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C ./configure install
perl /Users/arkilic/epics/base-3.14.12.4/bin/darwin-x86/makeMakefile.pl O.darwin-x86 ../..
mkdir O.Common
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C O.darwin-x86 -f ../Makefile TOP=../.. T_A=darwin-x86 install
perl /Users/arkilic/epics/base-3.14.12.4/bin/darwin-x86/convertRelease.pl checkRelease
Definition of PVACLIENT conflicts with PVACCESS support.
In this application a RELEASE file defines
PVACLIENT =
but PVACCESS at /Users/arkilic/EPICS-CPP-4.5.0-rc1/pvAccessCPP defines
PVACLIENT = /Users/arkilic/EPICS-CPP-4.5.0-rc1/pvaClientCPP
Definition of NORMATIVETYPES conflicts with PVACCESS support.
In this application a RELEASE file defines
NORMATIVETYPES =
but PVACCESS at /Users/arkilic/EPICS-CPP-4.5.0-rc1/pvAccessCPP defines
NORMATIVETYPES = /Users/arkilic/EPICS-CPP-4.5.0-rc1/normativeTypesCPP
Definition of PVACLIENT conflicts with PVDATA support.
In this application a RELEASE file defines
PVACLIENT =
but PVDATA at /Users/arkilic/EPICS-CPP-4.5.0-rc1/pvDataCPP defines
PVACLIENT = /Users/arkilic/EPICS-CPP-4.5.0-rc1/pvaClientCPP
Definition of NORMATIVETYPES conflicts with PVDATA support.
In this application a RELEASE file defines
NORMATIVETYPES =
but PVDATA at /Users/arkilic/EPICS-CPP-4.5.0-rc1/pvDataCPP defines
NORMATIVETYPES = /Users/arkilic/EPICS-CPP-4.5.0-rc1/normativeTypesCPP
make[2]: *** [checkRelease] Error 1
make[1]: *** [install.darwin-x86] Error 2
make: *** [configure.install] Error 2
I tried pvaccess server on Windows 10 I am unable to add a record to the server.
I tried it with pvapy 4.0.3 and 4.0.2 version.
My python installation is 3.9 on windows
The error is as below.
server.addRecord(sd.ioc+sd.rscan_name+':block_table', blk_tablePV)
Boost.Python.ArgumentError: Python argument types in
PvaServer.addRecord(PvaServer, str, PvObject)
did not match C++ signature:
addRecord(class PvaServer {lvalue}, class std::basic_string<char,struct std::char_traits,class std::allocator > channelName, class PvObject pvObject, class boost::python::api::object onWriteCallback)
Same test environment as #17 terminated with unhandled exception. Not sure if this issue is with pvaPy or pvaClientCPP. No dis/reconnect involved here.
1.0 1.0
terminate called after throwing an instance of 'std::runtime_error'
what(): PvaClientMonitor::releaseEvent did not call poll
Aborted
epics-base 7.0.3.1 is now available on conda-forge.
I think it'd make sense to have pvaPy there as well. It would ensure it's built with compatible versions of numpy and boost.
I have a repo ready: https://github.com/beenje/staged-recipes/tree/pvapy/recipes/pvaPy
I'd like your approval before to submit it to conda-forge of course.
And would other contributors be interested to be added as maintainer?
PvObject.toJSON()
does not properly escape strings which can result in malformed JSON. For example,
>>> pva.PvObject({'foo': pva.STRING}, {'foo': '"bar"'}).toJSON(True)
'{\n "foo": ""bar""\n}'
This cannot be loaded by e.g., json.loads
. The correct output is
>>> json.dumps({'foo': '"bar"'})
'{"foo": "\\"bar\\""}'
The following deprecated methods of pvaClientCPP are called:
static PvaClientPtr create() EPICS_DEPRECATED
and
static PvaClientMonitorPtr create(
PvaClientPtr const &pvaClient,
std::string const & channelName,
std::string const & providerName,
std::string const & request,
PvaClientChannelStateChangeRequesterPtr const & stateChangeRequester
= PvaClientChannelStateChangeRequesterPtr(),
PvaClientMonitorRequesterPtr const & monitorRequester
= PvaClientMonitorRequesterPtr() ) EPICS_DEPRECATED;
https://github.com/mrkraimer/pvaPy
has changes so that these two methods are no longer called.
I want to do some more testing before a pull request is created.
For now anyone who is interested can clone the mrkraimer repository.
pvaPy builds on CloudBees fail the EPICS Base check:
I'm not sure if this a pvapy issue or upstream in EPICS, but removing a record with a connected channel throws an error and causes python to temrinate.
s = pva.PvaServer('foo', pva.PvObject({'value': pva.INT}))
c = pva.Channel('foo')
c.get()
s.removeRecord('foo')
results in
terminate called after throwing an instance of 'std::bad_weak_ptr'
what(): bad_weak_ptr
I'm using pvapy=1.6.0
and epics-base=7.0.2.2
.
Trying to configure pvaPy with INSTALL_LOCATION given
tar -xaf EPICS-CPP-4.6.0.tar.gz
cd EPICS-CPP-4.6.0/
make -j4 EPICS_BASE=/home/mdavidsaver/work/epics/base-git INSTALL_LOCATION=$PWD/root
make -j4 EPICS_BASE=/home/mdavidsaver/work/epics/base-git INSTALL_LOCATION=$PWD/root config.pvaPy
make -j4 EPICS_BASE=/home/mdavidsaver/work/epics/base-git INSTALL_LOCATION=$PWD/root pvaPy
checking for boostlib >= 1.40... yes
configure: Using boost version 105500
checking whether the Boost::Python library is available... yes
checking for exit in -lboost_python... yes
checking for Boost.NumPy library... no
checking how to run the C++ preprocessor... g++ -E
checking for EPICS base directory /home/mdavidsaver/work/epics/base-git... yes
checking for EPICS Base version >= 3.14.12... yes
checking for EPICS Base libraries for linux-x86_64... yes
checking for EPICS4 directory /home/mdavidsaver/work/epics/EPICS-CPP-4.6.0... no
configure: error: "could not find pvAccess installation: no header file /home/mdavidsaver/work/epics/base-git/include/epicsVersion.h"
Makefile:46: recipe for target 'configure' failed
make[1]: *** [configure] Error 1
make[1]: Leaving directory '/home/mdavidsaver/work/epics/EPICS-CPP-4.6.0/pvaPy'
Makefile:140: recipe for target 'pvaPy/configure/RELEASE.local' failed
make: *** [pvaPy/configure/RELEASE.local] Error 2
The epicsVersion.h header does exist at the location mentioned
$ ls /home/mdavidsaver/work/epics/base-git/include/epicsVersion.h
/home/mdavidsaver/work/epics/base-git/include/epicsVersion.h
Recently the asyncGet
was added, which works as expected.
But the exceptions (e.g. Timeout) are just logged and not propagated to the user.
I see two possibilities how this could be added (there are probably more):
Channel.450.cpp has a blocking call to connect() in its constructor (Channel.440.cpp does not). This makes it inefficient to connect multiple channels in parallel, and throws an exception if the channel isn't available at the time of construction.
In my mind there is no need to block until an method is called which needs the connection to be established (eg. 'get()').
I am trying to compile and install it on NVIDIA Jetson TX2 using the automated compiling solution. but got errors as follow. appreciate if someone can help to figure out a solution
configure: Verifying boost python library
checking whether boost_python37 is the correct library... no
checking whether boost_python37 is the correct library... (cached) no
checking whether boost_python37 is the correct library... (cached) no
checking whether boost_python37 is the correct library... (cached) no
checking whether boost_python37 is the correct library... (cached) no
checking whether boost_python is the correct library... no
checking whether boost_python is the correct library... (cached) no
checking whether boost_python3 is the correct library... no
configure: error: in /home/zliu/pvaPy/tools/local/pvapy-local/build/pvaPy-4.0.3': configure: error: Could not verify boost python library; check your boost installation See
config.log' for more details
Makefile:53: recipe for target 'configure' failed
make[2]: *** [configure] Error 1
make[2]: Leaving directory '/home/zliu/pvaPy/tools/local/pvapy-local/build/pvaPy-4.0.3'
Makefile:11: recipe for target 'build' failed
make[1]: *** [build] Error 1
make[1]: Leaving directory '/home/zliu/pvaPy/tools/local/pvapy-local'
Makefile:111: recipe for target 'package-pvapy-local' failed
make: *** [package-pvapy-local] Error 2
The current Python API doesn't seem to allow creating a structure with anything other than a default type ID.
If this is the the case pvaPy will not be usable in a real system, since code written in C++/Java is setting the type IDs when creating structures and expecting to them to be set when receiving them.
PvObject does have constructors which take a type ID, so these just need to be exposed in pvaccess.PvObject.cpp
.def(init<boost::python::dict,const std::string &>(args("structureDict","typeId")))
Which allows structures with type IDs to be created in Python
print PvObject({'value' : DOUBLE}, 'epics:nt/NTScalar:1.0')
However this only allows for creation of top-level IDs. It doesn't allow for setting the type ID of lower-level structures. It looks difficult to create a general structure, including type IDs, using a single dictionary. Since dictionaries, tuples and lists all have special meanings (as structures, unions and arrays) it's not easy to supply an additional string.
It would possible to do it this way
struc3 = PvObject({ 'leaf' : DOUBLE }, 'type3')
struc2 = PvObject({ 'struc3' : struc3 }, 'type2')
struc1 = PvObject({ 'struc2' : struc2 }, 'type1')
However this requires a change to PyPvDataUtility as the latter loses the type IDs when you use a PvObject to create a subfield. I have this solution for this which generates
type1 type2 struc2 type3 struc3 double leaf 0
as required. I'll raise the pull request.
Similar comments also apply to structure arrays. I have a similar fix to that for structures, which I'll include in the pull request.
Hi,
I am trying to run the examples described at the README file but I am facing the following issue:
(py36) slepicka@dhcp-swlan-public-81-101:~/sandbox/ $ ipython
Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 13:14:59)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from pvaccess import *
In [2]: pv = PvObject({'x': INT, 'y': INT})
In [3]: pvaServer = PvaServer('pair', pv)
2018-03-13T11:21:58.977 Requested provider '<all>' not found
2018-03-13T11:21:58.977 ServerContext configured with not Providers will do nothing!
VERSION : pvAccess Server v6.0.1-SNAPSHOT
PROVIDER_NAMES :
BEACON_ADDR_LIST :
AUTO_BEACON_ADDR_LIST : 1
BEACON_PERIOD : 15
BROADCAST_PORT : 5076
SERVER_PORT : 5075
RCV_BUFFER_SIZE : 16384
IGNORE_ADDR_LIST:
INTF_ADDR_LIST : 0.0.0.0
Here are some information about versions:
pvaPy installs setup scripts for posix and C shells in INSTALL_BIN, named setup.sh and setup.csh
Such unqualified names are likely to create name clash situations, especially when trying to package pvaPy, which would install these scripts in a larger directory for binaries.
Would it be better to prefix the names, calling them e.g. pvaPy-setup.* ?
Is it possible to enable access security using these python bindings?
I wondered if there was an equivalent to doing something like the file based definitions (below) using pvapy.
UAG(uag) {user1,user2}
HAG(hag) {host1,host2}
ASG(DEFAULT) {
RULE(1,READ)
RULE(1,WRITE) {
UAG(uag)
HAG(hag)
}
}
I'm getting an error when trying to request fields from the same struct. Here is the pv structure:
structure
structure obj1
float[] x
float[] y
structure obj2
float[] x
float[] y
When I get the PV with the request descriptor 'obj1.x,obj2.y' I get the following error:
ERROR Channel: channel Ch1 PvaClientChannel::createMonitor invalid pvRequest: Can't construct Structure, duplicate fieldName obj1
While trying the examples listed in the Readme I realized it's possible to use pvaPy not only for pvAccess, but also for ChannelAccess. I like the interface of the library even for use with CA. Do you think it would at all be possible to compile a "CA-only"-version of pvaPy, i.e. without EPICSv4 available at compile and/or run time? Features that depend on v4 would of course not be available.
The reason I'm asking is that it would be nice to start using pvaPy in scenarios where only EPICSv3 is available. Later on, as more setups transition to EPICSv4, the Python interface would still be the same and the transition on that end would be easier.
I have not made any attempts at this myself, I wanted to hear your opinion first. I hope it's not too crazy of a question.
Since 1.63, Boost includes libboost_numpy and there is no need to install an extra dependency.
However, the headers are under boost/python/numpy.hpp
and the namespace is boost::python::numpy
.
I've patched the paths and namespaces and haven't seen a problem while building.
Such as:
import pvaccess
print(pvaccess.__version__)
One useful tool: https://github.com/warner/python-versioneer
Hello guys! At first, nice work, thank you!
We're trying to update timeStamp of a PV at the moment it is being assigned if I can say that way. The way we're doing seems to be not supported by the library, and we wonder if it would have a good reason to forbid such timeStamp assignment, probably yes... but then, it has a way to do it? We just need to have that timeStamp available at the PV for syncing with another service...
import pvaccess
timeStamp = pvaccess.PvTimeStamp(12345678, 12)
timeStamp
<pvaccess.pvaccess.PvTimeStamp at 0x7f8e6b536050>
print(timeStamp)
time_t
long secondsPastEpoch 12345678
int userTag -1
int nanoseconds 12
pvTest = pvaccess.PvObject({'value':pvaccess.STRING, 'timeStamp':pvaccess.PvTimeStamp}, {'value':'pvTest001', 'timeStamp':timeStamp})
InvalidArgument: Unrecognized structure type for field name timeStamp
Thank you for any guidance!
/ Douglas B Beniz
The following is putTime.py:
from pvapy import Channel, PvTimeStamp
channel = Channel('PVRdouble')
timestamp = PvTimeStamp(10, 100,1)
print('start value=',channel.get('timeStamp'))
channel.put(timestamp,'record[process=false]field(timeStamp)')
val = channel.get('value,alarm,timeStamp')
print('after put timeStamp value=',channel.get('timeStamp'))
It uses the database from exampleCPP
When the database is started and the following is issued
python putTime.py
The following is the result
start value= structure
time_t timeStamp
long secondsPastEpoch 0
int nanoseconds 0
int userTag 0
after put timeStamp value= structure
time_t timeStamp
long secondsPastEpoch 0
int nanoseconds 0
int userTag 0
Maybe the problem is that none of the PvAlarm and PyTimeStamp code has a get method?
I just had issues with segfault when using pvapy. In the end (i hope) it turned out that in the installation of pvapy it also installs numpy 1.21.0 and it seem that that version causes a seg fault at least when trying to creeate a pvaccess.NtScalar object.
By reinstalling numpy version 1.20.3 i do not get this issue.
pvaPy 2.0.0
Base (C++) 7.0.3.1
Python 2.7
On RHEL7, the sphinx_rtd_theme
can be installed from EPEL as python2-sphinx_rtd_theme-0.2.4-3.el7.noarch.rpm
. (This pulls a few dependencies for the fonts.)
After that, the pvaPy documentation build still fails with:
make[1]: Entering directory `/home/DCS/langer/work/CODAC/m-python-pvapy/target/pvaPy-2.0.0/documentation'
. /home/DCS/langer/work/CODAC/m-python-pvapy/target/pvaPy-2.0.0/bin/linux-x86_64/pvapy_setup_full.2.7.sh; make -C sphinx default
make[2]: Entering directory `/home/DCS/langer/work/CODAC/m-python-pvapy/target/pvaPy-2.0.0/documentation/sphinx'
export PATH=`dirname /usr/bin/sphinx-build`:/usr/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/DCS/langer/bin && make -f Makefile.sphinx html
make[3]: Entering directory `/home/DCS/langer/work/CODAC/m-python-pvapy/target/pvaPy-2.0.0/documentation/sphinx'
sphinx-build -b html -d _build/doctrees . _build/html
Running Sphinx v1.1.3
loading pickled environment... not yet created
[ERROR] Theme error:
[WARN] no theme named 'sphinx_rtd_theme' found (missing theme.conf?)
[WARN] make[3]: *** [html] Error 1
make[3]: Leaving directory `/home/DCS/langer/work/CODAC/m-python-pvapy/target/pvaPy-2.0.0/documentation/sphinx'
make[2]: Leaving directory `/home/DCS/langer/work/CODAC/m-python-pvapy/target/pvaPy-2.0.0/documentation/sphinx'
make[1]: Leaving directory `/home/DCS/langer/work/CODAC/m-python-pvapy/target/pvaPy-2.0.0/documentation'
[WARN] make[2]: *** [html] Error 2
[WARN] make[1]: *** [default] Error 2
[WARN] make: *** [doc] Error 2
What am I missing?
Hi.
Sorry but I have some questions which I am struggling to find answers to.
I can subscribe to a PV and get its value change events, but I cannot seem to find a way to supply a callback for notification when the PV disconnects. Is there a way to do this that I am missing?
Is it possible to do a blocking/waiting put? E.g. something like: chan.put('MY:PV', 10, wait=True)
Thanks
Extracting C macro definitions w/ text processing tools like sed is fragile (broken by epics-base/pvAccessCPP#21). Better to use AC_PREPROC_IFELSE() to do the version range check w/ the C preprocessor.
https://github.com/epics-base/pvaPy/blob/master/tools/autoconf/m4/ax_epics4.m4#L103
https://www.gnu.org/software/autoconf/manual/autoconf-2.63/html_node/Running-the-Preprocessor.html
pvaPy generates a shared library called pvaccess.so
This is a bad choice of name, as pvAccess itself creates libpvAccess.so. Such ambiguous naming will be a source of confusion and mistakes.
Please chose a name that is more unique and comprehensive.
Hello again...
We're now trying to set an alarm to a PV.
Firstly, being honest, I still have a question if pvaPy should do this (in fact I'm helping someone else, so, I'm still understanding the requirements and why they decided to use pvaPy).
Then, what is desired (and my questions are: first, is possible to do it using pvaPy, second, how to do it):
Go to what we (they) did (and the alarms were not set automatically, as I expected because we didn't programme anything to set the alarms considering the value):
from pvaccess import *
# assigning some objects
# ----------------------------------------------
# PvTimeStamp(<long secondsPastEpoch>,
# <int nanoseconds>,
# <int userTag=-1>)
# ----------------------------------------------
ts = PvTimeStamp(12345678, 12)
# ----------------------------------------------
# PvAlarm(<int severity>,
# <int status>,
# <string message>)
# ----------------------------------------------
alarm = PvAlarm(13,-1,'testing an error!')
# creating a PV object
pv = PvObject( {'value': FLOAT,
'timeStamp': ts,
'alarm': alarm,
'descriptor': STRING,
'lo': FLOAT,
'lolo': FLOAT,
'hi': FLOAT,
'hihi': FLOAT },
{'value': 250.67
'timeStamp': ts.toDict(),
'alarm': alarm.toDict(),
'descriptor': 'A first test PV name 001',
'lo': -321.15,
'lolo': -321.75,
'hi': 32.65,
'hihi': 33.00} )
print(pv)
# ----------------------------------------------
# output
# ----------------------------------------------
douglasbeniz@douglasbeniz-thinkpad:~/Documents$ python pvaPy_example.py
structure
string descriptor A first test PV name 001
float hi 32.65
float lo -321.15
time_t timeStamp
long secondsPastEpoch 12345678
int userTag -1
int nanoseconds 12
alarm_t alarm
int status -1
string message testing an error!
int severity 13
float hihi 33
float lolo -321.75
float value 250.67
Hi. I am unable to put an array where any value contains a string longer than 6 digits using the method Channel::putScalarArray. However, pvput works fine on the command line.
I also tried constructing a PvScalarArray of type ULONG, but a pvaccess.PvScalarArray is not accepted by Channel::putScalarArray.
Below are the database I used and the commands which can be run to reproduce the error. I am using pvapy 2.2
(dgro_asub) jsparger@spectre:dgro_asub$ pip freeze | grep pvapy
pvapy==2.2.0
record(waveform, "Addresses")
{
field(DESC, "Addresses")
field(FTVL, "ULONG")
field(NELM, "3")
}
In [1]: import pvaccess
In [2]: c = pvaccess.Channel("Addresses")
In [3]: print(c.get())
epics:nt/NTScalarArray:1.0
uint[] value [123456]
alarm_t alarm
int severity 0
int status 0
string message NO_ALARM
time_t timeStamp
long secondsPastEpoch 1591882692
int nanoseconds 459499857
int userTag 0
structure display
double limitLow 0
double limitHigh 0
string description Addresses
string units
int precision 0
enum_t form
int index 0
string[] choices ["Default", "String", "Binary", "Decimal", "Hex", "Exponential", "Engineering"]
control_t control
double limitLow 0
double limitHigh 0
double minStep 0
valueAlarm_t valueAlarm
boolean active false
double lowAlarmLimit nan
double lowWarningLimit nan
double highWarningLimit nan
double highAlarmLimit nan
int lowAlarmSeverity 0
int lowWarningSeverity 0
int highWarningSeverity 0
int highAlarmSeverity 0
byte hysteresis 0
In [4]: c.putScalarArray([123456])
In [5]: c.get()["value"]
Out[5]: array([123456], dtype=uint32)
In [6]: c.putScalarArray([1234567])
---------------------------------------------------------------------------
PvaException Traceback (most recent call last)
<ipython-input-6-4152885d1ebd> in <module>
----> 1 c.putScalarArray([1234567])
PvaException: parseToPOD: Extraneous characters
In [7]: quit()
(dgro_asub) jsparger@spectre:dgro_asub$ pvput Addresses [1234567]
Old : 2020-06-11 15:39:19.060 [123456]
New : 2020-06-11 15:43:26.610 [1234567]
Problem:
PV servers has difficultly keeping up reading PV objects with very large struct arrays. The same issue appears with both mirror server and p2p.
Steps to reproduce:
The pvapy client receives the data at 4Hz. The TCP-rx thread in the mirror server is maxed out and the perf profile shows that it is spending most time creating and deleting struct arrays (see graph below):
Both the mirror server and pv client can be optimized by caching the PV object structure after the connection is initialized.
This does not work and will cause python to hang:
data = PvObject({'value': INT}, {'value': 1})
server = PvaServer('foo', data, print)
channel = Channel('foo')
channel.put(data)
However, creating a server without a write callback works fine.
Recently when working with PvaPy I have been getting a Segmentation Fault when trying to create a PV.
I have been working with EPICS version R7.0.5 and pvapy version 3.1.0.
The simplest case where the fault occurs is when running:
from pvaccess import NtScalar, INT
pv = NtScalar(INT)
The backtrace for the above program is:
(gdb) bt
#0 0x00007f7a28e8d2f9 in PyDict_GetItemWithError () from /lib64/libpython3.8.so.1.0
#1 0x00007f7a23ac20bc in PyArray_GetCastingImpl ()
from /usr/local/lib64/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so
#2 0x00007f7a23ac2d88 in PyArray_GetCastSafety ()
from /usr/local/lib64/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so
#3 0x00007f7a23b71765 in PyArray_EquivTypes ()
from /usr/local/lib64/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so
#4 0x00007f7a260c19fa in boost::python::numpy::equivalent(boost::python::numpy::dtype const&, boost::python::numpy::dtype const&) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_numpy38.so.1.72.0
#5 0x00007f7a260c230c in ?? () from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_numpy38.so.1.72.0
#6 0x00007f7a262e01ce in boost::python::converter::rvalue_from_python_stage1(_object*, boost::python::converter::registration const&) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_python38.so.1.72.0
#7 0x00007f7a2789c8ab in PyPvDataUtility::updateFieldArrayFromInt(boost::python::api::object const&, std::string const&, std::vector<std::tr1::shared_ptr<epics::pvData::Field const>, std::allocator<std::tr1::shared_ptr<epics::pvData::Field const> > >&, std::vector<std::string, std::allocator<std::string> >&) () from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#8 0x00007f7a278a0e44 in PyPvDataUtility::updateFieldArrayFromDict(boost::python::dict const&, std::vector<std::tr1::shared_ptr<epics::pvData::Field const>, std::allocator<std::tr1::shared_ptr<epics::pvData::Field const> > >&, std::vector<std::string, std::allocator<std::string> >&) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#9 0x00007f7a278a12e0 in PyPvDataUtility::createStructureFromDict(boost::python::dict const&, std::string const&) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#10 0x00007f7a2787e867 in PvObject::PvObject(boost::python::dict const&, std::string const&) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#11 0x00007f7a2786db59 in NtType::NtType(boost::python::dict const&, std::string const&) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#12 0x00007f7a278693fa in NtScalar::NtScalar(PvType::ScalarType) () from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
--Type <RET> for more, q to quit, c to continue without paging--
#13 0x00007f7a27822f5c in boost::python::objects::make_holder<1>::apply<boost::python::objects::value_holder<NtScalar>, boost::mpl::vector1<PvType::ScalarType> >::execute(_object*, PvType::ScalarType) () from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#14 0x00007f7a27800aeb in boost::python::objects::caller_py_function_impl<boost::python::detail::caller<void (*)(_object*, PvType::ScalarType), boost::python::default_call_policies, boost::mpl::vector3<void, _object*, PvType::ScalarType> > >::operator()(_object*, _object*) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#15 0x00007f7a262e9611 in boost::python::objects::function::call(_object*, _object*) const ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_python38.so.1.72.0
#16 0x00007f7a262e9958 in ?? () from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_python38.so.1.72.0
#17 0x00007f7a262f21f3 in boost::python::detail::exception_handler::operator()(boost::function0<void> const&) const ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_python38.so.1.72.0
#18 0x00007f7a277d4193 in boost::detail::function::function_obj_invoker2<boost::_bi::bind_t<bool, boost::python::detail::translate_exception<PvaException, void (*)(PvaException const&)>, boost::_bi::list3<boost::arg<1>, boost::arg<2>, boost::_bi::value<void (*)(PvaException const&)> > >, bool, boost::python::detail::exception_handler const&, boost::function0<void> const&>::invoke(boost::detail::function::function_buffer&, boost::python::detail::exception_handler const&, boost::function0<void> const&) () from /usr/local/lib64/python3.8/site-packages/pvaccess/pvaccess.so
#19 0x00007f7a262f1fcd in boost::python::handle_exception_impl(boost::function0<void>) ()
from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_python38.so.1.72.0
#20 0x00007f7a262e6c63 in ?? () from /usr/local/lib64/python3.8/site-packages/pvaccess/lib/linux-x86_64/libboost_python38.so.1.72.0
#21 0x00007f7a28e80f72 in _PyObject_MakeTpCall () from /lib64/libpython3.8.so.1.0
#22 0x00007f7a28e274c9 in method_vectorcall.cold () from /lib64/libpython3.8.so.1.0
#23 0x00007f7a28e8bc3f in PyObject_Call () from /lib64/libpython3.8.so.1.0
#24 0x00007f7a28f24225 in slot_tp_init () from /lib64/libpython3.8.so.1.0
#25 0x00007f7a28e80e0a in _PyObject_MakeTpCall () from /lib64/libpython3.8.so.1.0
#26 0x00007f7a28f3bb97 in _PyEval_EvalFrameDefault () from /lib64/libpython3.8.so.1.0
#27 0x00007f7a28efdf67 in _PyEval_EvalCodeWithName () from /lib64/libpython3.8.so.1.0
#28 0x00007f7a28eff143 in PyEval_EvalCode () from /lib64/libpython3.8.so.1.0
--Type <RET> for more, q to quit, c to continue without paging--
#29 0x00007f7a28f9462a in run_eval_code_obj () from /lib64/libpython3.8.so.1.0
#30 0x00007f7a28fa6d92 in run_mod () from /lib64/libpython3.8.so.1.0
#31 0x00007f7a28e58cf0 in PyRun_FileExFlags () from /lib64/libpython3.8.so.1.0
#32 0x00007f7a28e5fa07 in PyRun_SimpleFileExFlags () from /lib64/libpython3.8.so.1.0
#33 0x00007f7a28fa895f in Py_RunMain () from /lib64/libpython3.8.so.1.0
#34 0x00007f7a28fa8ae9 in Py_BytesMain () from /lib64/libpython3.8.so.1.0
#35 0x00007f7a27de1493 in __libc_start_main () from /lib64/libc.so.6
#36 0x000055f680b3b78e in _start ()
The shared library pvaccess.dylib does not work on OSX Yosemite(not sure about other OSX versions but we tested on 4 different machines). The issue goes away if .dylib -> .so linked manually.
Hi,
I've encountered an odd issue, but I am not sure if this is a feature or a bug, I am hoping you can clarify.
I notice that when using the set
method on PvObject the order of key-value pairs in the dictionary passed to the method can affect subscribers. For example:
pv1 = pvapy.PvObject(.....)
# these two are not the same
pv1.set({'a': 1, 'b': 2})
pv1.set({'b': 2, 'a': 1})
For a full reproducible example, please see the snippet below:
#!/usr/bin/env python
import time
from datetime import datetime
import pvapy as pv
EPOCH = datetime.utcfromtimestamp(0)
def timestampnow():
dt = datetime.now() - EPOCH
seconds_since_epoch = int(dt.total_seconds())
nanoseconds = int(dt.microseconds * 1e3)
return pv.PvTimeStamp(seconds_since_epoch, nanoseconds)
# my PV
pv1 = pv.PvObject(
{
"value": pv.INT,
"timeStamp": {
"secondsPastEpoch": pv.LONG,
"nanoseconds": pv.INT,
"userTag": pv.INT,
},
},
{
"value": 0,
"timeStamp": {
"secondsPastEpoch": 0,
"nanoseconds": 0,
"userTag": 0,
},
},
)
# setup a server
server = pv.PvaServer("mypvtest", pv1)
# callback
def echo(x):
print(
"** CLIENT **",
datetime.fromtimestamp(x["timeStamp"]["secondsPastEpoch"]).strftime(
"%Y-%m-%d %H:%M:%S.%f"
),
x["value"]
)
# client - subscriber
channel = pv.Channel("mypvtest", pv.ProviderType.PVA)
channel.subscribe("mon", echo)
channel.startMonitor()
counter = 0
while True:
x = counter
ts = timestampnow()
# replace this line with the other below it and notice the difference in output
pv1.set({"timeStamp": ts, "value": x})
#pv1.set({"value": x,"timeStamp": ts})
print(
"** SERVER **",
datetime.fromtimestamp(ts["secondsPastEpoch"]).strftime("%Y-%m-%d %H:%M:%S.%f"),
x
)
time.sleep(1)
counter = counter + 1 if counter < 9 else 0
With timeStamp first we see this output:
** SERVER ** 2021-05-28 14:51:21.000000 0
** CLIENT ** 2021-05-28 14:51:21.000000 0
** SERVER ** 2021-05-28 14:51:22.000000 1
** CLIENT ** 2021-05-28 14:51:22.000000 0
** SERVER ** 2021-05-28 14:51:23.000000 2
** CLIENT ** 2021-05-28 14:51:23.000000 1
** SERVER ** 2021-05-28 14:51:24.000000 3
** CLIENT ** 2021-05-28 14:51:24.000000 2
** SERVER ** 2021-05-28 14:51:25.000000 4
** CLIENT ** 2021-05-28 14:51:25.000000 3
** SERVER ** 2021-05-28 14:51:26.000000 5
** CLIENT ** 2021-05-28 14:51:26.000000 4
** SERVER ** 2021-05-28 14:51:27.000000 6
** CLIENT ** 2021-05-28 14:51:27.000000 5
** SERVER ** 2021-05-28 14:51:28.000000 7
** CLIENT ** 2021-05-28 14:51:28.000000 6
** SERVER ** 2021-05-28 14:51:29.000000 8
** CLIENT ** 2021-05-28 14:51:29.000000 7
** SERVER ** 2021-05-28 14:51:30.000000 9
** CLIENT ** 2021-05-28 14:51:30.000000 8
** SERVER ** 2021-05-28 14:51:31.000000 0
** CLIENT ** 2021-05-28 14:51:31.000000 9
** SERVER ** 2021-05-28 14:51:32.000000 1
** CLIENT ** 2021-05-28 14:51:32.000000 0
** SERVER ** 2021-05-28 14:51:33.000000 2
** CLIENT ** 2021-05-28 14:51:33.000000 1
** SERVER ** 2021-05-28 14:51:34.000000 3
** CLIENT ** 2021-05-28 14:51:34.000000 2
whereas the other way around we see this output.
** SERVER ** 2021-05-28 14:52:59.000000 0
** CLIENT ** 2021-05-28 14:52:59.000000 0
** SERVER ** 2021-05-28 14:53:00.000000 1
** CLIENT ** 2021-05-28 14:52:59.000000 1
** SERVER ** 2021-05-28 14:53:01.000000 2
** CLIENT ** 2021-05-28 14:53:00.000000 2
** SERVER ** 2021-05-28 14:53:02.000000 3
** CLIENT ** 2021-05-28 14:53:01.000000 3
** SERVER ** 2021-05-28 14:53:03.000000 4
** CLIENT ** 2021-05-28 14:53:02.000000 4
** SERVER ** 2021-05-28 14:53:04.000000 5
** CLIENT ** 2021-05-28 14:53:03.000000 5
** SERVER ** 2021-05-28 14:53:05.000000 6
** CLIENT ** 2021-05-28 14:53:04.000000 6
** SERVER ** 2021-05-28 14:53:06.000000 7
** CLIENT ** 2021-05-28 14:53:05.000000 7
** SERVER ** 2021-05-28 14:53:07.000000 8
** CLIENT ** 2021-05-28 14:53:06.000000 8
** SERVER ** 2021-05-28 14:53:08.000000 9
** CLIENT ** 2021-05-28 14:53:07.000000 9
** SERVER ** 2021-05-28 14:53:09.000000 0
** CLIENT ** 2021-05-28 14:53:08.000000 0
** SERVER ** 2021-05-28 14:53:10.000000 1
** CLIENT ** 2021-05-28 14:53:09.000000 1
** SERVER ** 2021-05-28 14:53:11.000000 2
** CLIENT ** 2021-05-28 14:53:10.000000 2
** SERVER ** 2021-05-28 14:53:12.000000 3
** CLIENT ** 2021-05-28 14:53:11.000000 3
You can see in the first one the times are in sync but the values are not, similarly, in the other one the values are in sync but not the times.
My question is, if this is a feature how can I set the both at the same time, so both timestamp and value are in sync with subscribers? If it is a bug, do you know of a workaround?
Thanks,
Tom
There is no way to stop a running PvaServer. There should be an explicit PvaServer.stop()
method or similar. It would also be nice if PvaServer was a context manager so we could do
with PvaServer() as server:
# Do stuff
I find that when monitoring a channel which becomes disconected, that after reconnection the callback is invoked when it should be, but the value passed is the last value before disconnect. pvget
shows the correct values after reconnect.
#!/usr/bin/env python
import time
import pvaccess as pva
chan = pva.Channel('cnt:x')
def proc(v):
print v.get()['value']
chan.subscribe('arbitrary', proc)
chan.startMonitor()
while True:
time.sleep(60)
Note: While this script only prints the value field, I check that no field values are changing after reconnect.
Output with 1 Hz counter PV.
8.0
9.0
10.0
0.0
1.0
2.0
3.0
error cnt:x cnt:x connection state DISCONNECTED
3.0
3.0
3.0
3.0
3.0
3.0
3.0
Output of pvget -m
cnt:x 8
cnt:x 9
cnt:x 10
cnt:x 0
cnt:x 1
cnt:x 2
cnt:x 3
cnt:x *** disconnected
cnt:x 0
cnt:x 1
cnt:x 2
cnt:x 3
cnt:x 4
cnt:x 5
cnt:x 6
cnt:x 7
I find that Channel::monitorThread
is feeding elements into it's queue, then release()ing them. This allows them to be reused, possible even before they are removed from the queue. The appropriate action is not to call release() for an element until the caller will no longer access it.
The pvaPy autoconf magic seems to look for the EPICS V4 bundle version (it calls it "EPICS4 version" and expects it to be >=4.4.0), while pvAccess has the pvAccess module version number in the file pvaVersionNum.h
(currently 4.1.2 on the 4.1 release branch).
It seems that make distclean does not remove
But it should, shouldn't it?
Hi,
I am unable to get pvaPy from the PyPI repository for my Raspberry Pi.
It would be great if wheel for the Raspberry/ARM are available on PyPI.
Till then I may have to do a manual compile and install.
Thank you in advance
Kuldeep
Just installed pvapy 4.0.3 and epics-base 7.0.6 from the epics conda channel with python 3.8. Existing code that was working with previous pvapy versions now fails, and I've traced it back to the following minimal example:
import pvaccess as pva
pva.PvObject({'foo': [pva.STRING]}) # Raises seg. fault
This is not limited to pva.STRING
types, but also occurs with pva.INT
and possibly others as well. Any suggestions? Otherwise I'll have to roll back.
I would like to set timeout for RpcClient, but there seems be no way to set it. The timeout is set as DefaultTimeout and its value is 1. It is convenient if there are getTimeout and setTimeout like Channel class. Do you have any plan for RpcClient timeout?
Hello,
In our group we are using pvapy for a number of applications but primarily to host a number of PvaServers. We then have a separate application that connects to the PVs on these servers using the Channel
client to monitor them for changes.
So far, the functionality of both applications has worked well, but we are now facing a number of issues with reconnection protocols. I have tried two different methods to account for PV reconnections so far and both have created their own problems. I'm not sure if these are a result of how I'm doing the reconnection using the library or something in our system...
The methods I've tested so far are:
This method works well if the PvaServer is stopped cleanly, but encounters problems if the PvaServer crashes unexpectedly. In this case we stop receiving updates for the PVs through the assigned onChanges
function, even though we can see the PVs updating with a pvmonitor
on the command line.
setConnectionCallback()
method. In this method we subscribe the onChanges
function and start monitoring the PV. When it disconnects, we stop monitoring the channel.This second option feels to me to be the cleaner of the two. However, we noticed alternating crashes between the PvaServer and the client application. We haven't yet worked out which of the two programs is responsible for the crashes. We also encountered some segmentation faults using this method.
In your opinion, what is the best way to handle these situations where potentially lots of PVs become disconnected at once? Do you have any advice for how to handle reconnections on the client side, and is there anything you might be able to suggest for the server side to improve the closure of PV connections?
Thanks for your help!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.