epics-base / p4p

Python bindings for the PVAccess network client and server.
BSD 3-Clause "New" or "Revised" License
26 stars 38 forks source link

Cannot write to a NTNDArray PV #125

Closed rosstitmarsh closed 1 year ago

rosstitmarsh commented 1 year ago

python: 3.11 p4p: 4.1.10

I am trying to update a PV with the NTNDArray type, but I cannot get it to work.

The example in example/image_server.py does not allow for changing the PV once created.

The notes in #24 did not help.

Server:

from p4p.nt import NTNDArray
from p4p.server import Server
from p4p.server.thread import SharedPV
import numpy as np

image = np.asarray([[0, 1], [1, 0]])

pv = SharedPV(nt=NTNDArray(), initial=image)

@pv.put
def handle(pv, op):
    pv.post(op.value())
    op.done()

Server.forever(providers=[{"test": pv}])

Client trying various methods to update the PV:

from p4p.nt import NTNDArray
from p4p.client.thread import Context
import numpy as np

image = np.asarray([[1, 0], [0, 1]])

context = Context("pva")

# Method 1
context.put("test", image)
# Traceback (most recent call last):
#   File "/home/user/test-ioc/image_client.py", line 10, in <module>
#     context.put("test", image)
#   File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/client/thread.py", line 368, in put
#     raise value
#   File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/client/raw.py", line 80, in builder
#     nt.assign(V, value)
#   File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/nt/__init__.py", line 84, in assign
#     self._assign(V, value)
#   File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/nt/ndarray.py", line 196, in assign
#     V.value = py
#     ^^^^^^^
#   File "src/p4p/_p4p.pyx", line 223, in p4p._p4p._Value.__setattr__
# RuntimeError: Only 1-d array can be assigned

# Method 2
context.put("test", image.flatten())
# Traceback (most recent call last):
#   File "/home/user/test-ioc/image_client.py", line 27, in <module>
#     context.put("test", image.flatten())
#   File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/client/thread.py", line 368, in put
#     raise value
# p4p._p4p.RemoteError: Unable to unwrap Value(id:epics:nt/NTNDArray:1.0, array([1, 0, 0, 1])) with <bound method NTNDArray.unwrap of <class 'p4p.nt.ndarray.NTNDArray'>>

# Method 3
context.put("test", NTNDArray().wrap(image))
# Traceback (most recent call last):
#   File "/home/user/test-ioc/image_client.py", line 36, in <module>
#     context.put("test", NTNDArray().wrap(image))
#   File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/client/thread.py", line 368, in put
#     raise value
#   File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/client/raw.py", line 75, in builder
#     V[None] = value
#     ~^^^^^^
#   File "src/p4p/_p4p.pyx", line 192, in p4p._p4p._Value.__setitem__
# ValueError: field "dimension" : Unable to assign struct[] with Array

Method 2 was the only one that produced an error in the server

Unexpected
Traceback (most recent call last):
  File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/server/raw.py", line 21, in value
    return self._unwrap(V)
           ^^^^^^^^^^^^^^^
  File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/nt/ndarray.py", line 191, in unwrap
    return V.view(klass.ntndarray)._store(value)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/nt/ndarray.py", line 62, in _store
    self.shape = shape or [0] # can't reshape if 0-d, so treat as 1-d if no dimensions provided
    ^^^^^^^^^^
ValueError: cannot reshape array of size 4 into shape (0,)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/server/thread.py", line 19, in _on_queue
    M(*args)
  File "/home/user/test-ioc/image_server.py", line 12, in handle
    pv.post(op.value())
            ^^^^^^^^^^
  File "/home/user/test-ioc/venv/lib/python3.11/site-packages/p4p/server/raw.py", line 23, in value
    raise ValueError("Unable to unwrap %r with %r"%(V, self._unwrap))
ValueError: Unable to unwrap Value(id:epics:nt/NTNDArray:1.0, array([1, 0, 0, 1])) with <bound method NTNDArray.unwrap of <class 'p4p.nt.ndarray.NTNDArray'>>

Do I need to use method 2 with a more complicated put handler in the server?

mdavidsaver commented 1 year ago

I think I have sorted this out. Please update and re-test with p4p==4.1.11a2 pvxslibs==1.2.4a3. These include two separate changes: mdavidsaver/pvxs@a9eea922b5c210f8d13e81238bf446f0ca5e2a9b and 4279599d553c523fd6c38b14f18d4225dc58d00d. Combined these should allow a modified image_server.py

diff --git a/example/image_server.py b/example/image_server.py
index 0251ab8..67c4eda 100644
--- a/example/image_server.py
+++ b/example/image_server.py
@@ -39,6 +39,11 @@ pv = SharedPV(nt=NTNDArray(),
               attrib={"plain":"hello",
                       "withmeta":myattr})

+@pv.put
+def handle(pv, op):
+    pv.post(op.value())
+    op.done()
+
 print('serving pv:', args.pvname)

 Server.forever(providers=[{

running with PV name face to be accessed like:

import numpy
from p4p.client.thread import Context
ctxt = Context()
I=ctxt.get('face')
ctxt.put('face', I)
ctxt.put('face', numpy.asarray([[1, 0], [0, 1]]))
rosstitmarsh commented 1 year ago

Thank you for the quick fix, it works great now.