BCDA-APS / adl2pydm

Convert MEDM's .adl files to PyDM's .ui format
Other
0 stars 4 forks source link

"Connected" text does not show #49

Closed prjemian closed 2 years ago

prjemian commented 4 years ago

When using the new PyDM screens with an AVT Mako camera, the green "Connected" text does not show on the PyDM screen although it shows in the caQtDM screen (shown to the left).

prjemian commented 4 years ago

Is there a visibility rule missing?

prjemian commented 4 years ago

Clipboard01

prjemian commented 4 years ago

That comes from ADSetup.adl. There is a rectangle that spans the background. Second widget in the ADL file (means it is stacked in the back, later widgets are stacked on top). Look at the rule for "Connected".

prjemian commented 2 years ago

Could be related to #61.

prjemian commented 2 years ago

Can show this with ADSimDetector instance: pydm -m "P=ad:,R=cam1:" ADSetup.ui

Here's a side-by-side view with caQtDM (left) & PyDM (right): Clipboard01

Does not work yet (#61 included).

prjemian commented 2 years ago

section from .adl file:

text {
    object {
        x=197
        y=259
        width=90
        height=20
    }
    "basic attribute" {
        clr=63
    }
    "dynamic attribute" {
        vis="if not zero"
        calc="0"
        chan="$(P)$(R)AsynIO.CNCT"
    }
    textix="Connected"
    align="horiz. centered"
}

Corresponding section from converted PyDM file:

    <widget class="PyDMLabel" name="text_3">
      <property name="geometry">
        <rect>
          <x>197</x>
          <y>259</y>
          <width>90</width>
          <height>20</height>
        </rect>
      </property>
      <property name="text">
        <string>Connected</string>
      </property>
      <property name="font" stdset="0">
        <font>
          <pointsize>10</pointsize>
        </font>
      </property>
      <property name="brush" stdset="0">
        <brush brushstyle="SolidPattern">
          <color alpha="255">
            <red>40</red>
            <green>147</green>
            <blue>21</blue>
          </color>
        </brush>
      </property>
      <property name="rules" stdset="0">
        <string>[{&quot;name&quot;: &quot;visibility&quot;, &quot;property&quot;: &quot;Visible&quot;, &quot;channels&quot;: [{&quot;channel&quot;: &quot;ca://${P}${R}AsynIO.CNCT&quot;, &quot;trigger&quot;: true}], &quot;expression&quot;: &quot;0&quot;}]</string>
      </property>
      <property name="toolTip">
        <string>text_3</string>
      </property>
      <property name="alignment">
        <set>Qt::AlignCenter</set>
      </property>
      <property name="styleSheet">
        <string notr="true">PyDMLabel#text_3 {
  color: rgb(40, 147, 21);
  }</string>
      </property>
    </widget>
prjemian commented 2 years ago

Here's the PyDM screen in QT's designer, showing the rules for the Disconnected widget.

Clipboard01

prjemian commented 2 years ago

Now, rules for the Connected widget.

Clipboard01

The Expression should be similar but it is not.

prjemian commented 2 years ago

The Expression must compare with the string value?

(pydm) prjemian@zap:~/.../BCDA-APS/adl2pydm$ caget ad:cam1:AsynIO.CNCT
ad:cam1:AsynIO.CNCT            Connect
prjemian commented 2 years ago

Correct. Confirmed by replacing the PV in the calc (${P}${R}AsynIO.CNCT) with a bo PV (gp:gp:bit1) that can be controlled independent of area detector. Then, change the Expression to compare =="Connect" or !="Connect".

Question: How to force Expression to use a number?

prjemian commented 2 years ago

Also, for the text widget with textix="Connected" in ADSetup.adl, there is an additional calc=0 setting under dynamic attribute that is not present in ther Disconnected case. How can adl2pydm resolve this?

prjemian commented 2 years ago

Compare with with the same widget (also Qt .ui file) rendered for caQtDM:

        <widget class="caLabel" name="caLabel_3">
            <property name="frameShape">
                <enum>QFrame::NoFrame</enum>
            </property>
            <property name="foreground">
                <color alpha="255">
                    <red>40</red>
                    <green>147</green>
                    <blue>21</blue>
                </color>
            </property>
            <property name="background">
                <color alpha="0">
                    <red>40</red>
                    <green>147</green>
                    <blue>21</blue>
                </color>
            </property>
            <property name="visibility">
                <enum>caLabel::IfNotZero</enum>
            </property>
            <property name="visibilityCalc">
                <string>0</string>
            </property>
            <property name="channel">
                <string>$(P)$(R)AsynIO.CNCT</string>
            </property>
            <property name="text">
                <string>Connected</string>
            </property>
            <property name="fontScaleMode">
                <enum>ESimpleLabel::WidthAndHeight</enum>
            </property>
            <property name="alignment">
                <set>Qt::AlignAbsolute|Qt::AlignHCenter|Qt::AlignVCenter</set>
            </property>
            <property name="geometry">
                <rect>
                    <x>197</x>
                    <y>259</y>
                    <width>90</width>
                    <height>20</height>
                </rect>
            </property>
        </widget>
prjemian commented 2 years ago

This is a tough one to solve. The PV in question is returning a string value and there is no easy way from PyDM to request a numerical value. If it were a bo record, could use the .RVAL field but that is not the case here. Here is a demo using a bo record (gp:gp:bit1). with Expressions in several ways. The first column is what the current rendering uses with ADSetup.adl, then second is using string comparisons (the string values are not available to the converter), and the third is using a numerical value from the .RVAL field.

bit is off

Clipboard01

bit is on

Clipboard02

The EPICS .dbd file shows:

    field(CNCT, DBF_MENU) {
        promptgroup("40 - Input")
        prompt("Connect/Disconnect")
        interest(1)
        menu(asynCONNECT)
        special(SPC_MOD)
    }
prjemian commented 2 years ago

It would help if PyDM had a mode to use numerical values instead of strings in these calcs. But there can be uses for string values, too. An option switch.

prjemian commented 2 years ago

Propose some resolution in a PR to PyDM.

prjemian commented 2 years ago

How does caQtDM handle the comparison here? Always request numerical DBF type? Sounds like a non-default option for PyDM to gain compatibility.

prjemian commented 2 years ago

The caQtDM code looks like it is a complicated decision:

                if((caFieldType == caSTRING || caFieldType == caENUM || caFieldType == caCHAR) && ptr->edata.dataB != (void*) 0) {
                    if(ptr->edata.dataSize < STRING_EXCHANGE_SIZE) {
                        memcpy(dataString, (char*) ptr->edata.dataB, (size_t) ptr->edata.dataSize);
                        dataString[ptr->edata.dataSize] = '\0';

                        // in case of enum we have to get the right string from the value
                        if(caFieldType == caENUM) {
                            QString String(dataString);
                            QStringList list;
                            //list = String.split(";");
                            list = String.split((QChar)27);
                            if((ptr->edata.fieldtype == caENUM)  && ((int) ptr->edata.ivalue < list.count() ) && (list.count() > 0)) {
                                if(list.at((int) ptr->edata.ivalue).trimmed().size() != 0)  {  // string seems to empty, give value
                                    QString strng = list.at((int) ptr->edata.ivalue);
                                    QByteArray ba = strng.toLatin1();
                                    strcpy(dataString, ba.data());
                                }
                            }
                        }

                    } else  {
                        char asc[MAX_STRING_LENGTH];
                        snprintf(asc, MAX_STRING_LENGTH, "Invalid channel data type %s", qasc(w->objectName()));
                        postMessage(QtDebugMsg, asc);
                        valid = false;
                        return true;
                    }
                }
prjemian commented 2 years ago

This type of decision must be handled in pydm with access to the actual PV used in the calc. Only with a connected channel can the field type and list of enumerations be determined.

prjemian commented 2 years ago

The PyDM code to evaluate rules is much shorter:


    def calculate_expression(self, widget_ref, idx, rule):
        """
        Evaluate the expression defined by the rule and emit the `rule_signal`
        with the new value.
        .. warning
            This method mutates the input rule in-place
        Returns
        -------
        None
        """
        rule['calculate'] = False

        vals = rule['values']
        enums = rule['enums']

        calc_vals = []
        for en, val in zip(enums, vals):
            try:
                calc_vals.append(en[val])
                continue
            except:
                calc_vals.append(val)

        eval_env = {'np': np,
                    'ch': calc_vals}
        eval_env.update({k: v
                         for k, v in math.__dict__.items()
                         if k[0] != '_'})

        try:
            expression = rule['rule']['expression']
            name = rule['rule']['name']
            prop = rule['rule']['property']
            val = eval(expression, eval_env)
            self.emit_value(widget_ref, name, prop, val)
        except Exception as e:
            logger.exception("Error while evaluating Rule.")

But this is the line that substitutes an enumerated text value from an integer value:

calc_vals.append(en[val])

We want to make this conversion the default but can switch it off for converted MEDM screens.

Need:

prjemian commented 2 years ago

As an alternative to a command-line option, this converter could set a property on each widget that would be used to prevent the default conversion. ONLY those widgets with the property set to True would bypass the conversion. This is easier and more automatic than passing the new command-line option from the application down to each widget.