epics-base / pvxs

PVA protocol client/server library and utilities.
https://mdavidsaver.github.io/pvxs/
Other
22 stars 31 forks source link

Not possible to distinguish between scalars and arrays of length 1 #69

Open simon-ess opened 4 months ago

simon-ess commented 4 months ago

Describe the bug It is not possible at the moment to distinguish between the following records via PVAccess:

record(waveform, "one") {
    field(FTVL, "STRING")
    field(NELM, "1")
    field(INP, {const: "foo"})
}

record(stringin, "onestring") {
    field(INP, {const: "bar"})
}

This ultimately manifests in the fact that fetching and processing data from a record that is a waveform of strings typically requires some additional checks, since you get something like

$ pvget one
one 2024-06-03 14:23:52.988  foo

whereas if the array has NELM > 1 then you instead get

$ pvget two
one 2024-06-03 14:23:52.988  [foo, bar]

which is typically handled differently in e.g. pyepics, p4p, etc.

Note that this is not really restricted to PVXS, or even PVAccessCPP, but also ChannelAccess. In channel access I believe that due to the protocol there is no possible fix; in PVAccess, it should be fixable since an array of length 1 of type string is sent as an NTScalar, whereas an array of length > 1 is sent as an NTScalarArray; it should be possible to send an array of length one also as an NTScalarArray, which should fix this.

To Reproduce Steps to reproduce the behavior:

  1. Start an IOC with the db file above
  2. get the values
  3. be sad

Expected behavior This is a bit murky, because I might be proposing a breaking change in how IOCs communicate over PVAccess. I think it would be more consistent to have arrays always send as NTScalarArrays instead of NTScalars if they are length 1. But there might be a good reason for the current behaviour.

Information (please complete the following):

$ pvxs/bin/darwin-x86/pvxinfo -D
Host: darwin-x86
Target: darwin-x86 Darwin clang
Toolchain
    __cplusplus = 201103
    clang 14.0.3 (clang-1403.0.22.14.1)
    GCC 4.2.1
    _LIBCPP_VERSION 160006
Versions
    PVXS 1.2.3 (1.3.1-5-g57f79ce747f062a32eed-dirty)
    EPICS 7.0.8.1-DEV
    libevent 2.2.0-alpha-dev
Runtime
    uname() -> Darwin CIN-716194 22.6.0 Darwin Kernel Version 22.6.0: Mon Apr 22 20:54:28 PDT 2024; root:xnu-8796.141.3.705.2~1/RELEASE_X86_64 x86_64
    epicsThreadGetCPUs() -> 16
    osiLocalAddr() -> 172.18.21.40
    osiSockDiscoverBroadcastAddresses() ->
        172.18.23.255
        172.18.23.255
        192.168.205.255
Effective Client config from environment
    EPICS_PVA_ADDR_LIST=172.18.23.255 192.168.205.255
    EPICS_PVA_AUTO_ADDR_LIST=NO
    EPICS_PVA_BROADCAST_PORT=5076
    EPICS_PVA_CONN_TMO=30
    EPICS_PVA_INTF_ADDR_LIST=0.0.0.0
    EPICS_PVA_NAME_SERVERS=
    EPICS_PVA_SERVER_PORT=5075
Effective Server config from environment
    EPICS_PVAS_AUTO_BEACON_ADDR_LIST=NO
    EPICS_PVAS_BEACON_ADDR_LIST=172.18.23.255 192.168.205.255
    EPICS_PVAS_BROADCAST_PORT=5076
    EPICS_PVAS_IGNORE_ADDR_LIST=
    EPICS_PVAS_INTF_ADDR_LIST=0.0.0.0
    EPICS_PVAS_SERVER_PORT=5075

Additional context Add any other context about the problem here.

anjohnson commented 4 months ago

The absence of a difference is baked a long way into the IOC code, deliberately so I believe (Bob D. might be able to explain that).

This isn't the only case where EPICS requires a client to know something about the PV it's connecting to. That also happens with an array of CHARs, is it a string or a series of 8-bit integers? The client has to know which it's meant to be to properly display the value.

simon-ess commented 4 months ago

While I was digging around I could see that CA probably fundamentally cannot distinguish between these two types, but I do think it should be possible to do so for PVA; given that we send the data types over the wire, we should be able to say that "this is an array, but its length is 1".

It's worth noting that one of the places that this decision is made at the moment is here; it's a very simply check that only looks at the length of the data and not the type of the data. This could be improved.

(This gives me flashbacks of coding in APL; it is actually a bit non-trivial to have arrays of length 1, although it is possible)

We can close this issue if this is truly how things should behave. But I think it is worth considering an improvement here.

mdavidsaver commented 4 months ago

The absence of a difference is baked a long way into the IOC code ...

Concretely, this difference is expressed to QSRV through struct dbChannel having an element type and maximum number of elements, but no minimum element count. So testing that maximum is, I think, the best which can be done generically with the present dbAccess API. (without hard coding knowledge about certain fields of certain record types)

https://github.com/mdavidsaver/pvxs/blob/57f79ce747f062a32eede1a630d33a5234e0808f/ioc/iocsource.cpp#L639

Of course, this still leaves cases where a "scalar" actually turns out to have zero elements. At which point things get messy...

https://github.com/mdavidsaver/pvxs/blob/57f79ce747f062a32eede1a630d33a5234e0808f/ioc/iocsource.cpp#L83-L85

... we should be able to say that "this is an array, but its length is 1".

I have had requests to allow modifications of field type mapping between PDB and PVA in places where there is no clear one-to-one relation. eg. coerce numeric to boolean. One ~obvious, though open ended, way to express this would be through info() tags.