How do DAQFactory buttons share one script across 64 channels?

David Krause11 min read
HMI / SCADAOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

DAQFactory 5.7x has no linked component template. Duplicating a button copies its properties and script, and editing one copy never changes the others. A 64-button screen showing pH and temperature for 32 batches therefore needs a different approach. Keep the per-button content identical and trivial, derive the channel from the component name or a user property, and hold the real logic in one place that every button calls. The same release also lacks pointers or references, so per-channel routines such as calibration take the channel name as a string and act on it through Execute(). Everything below applies to 5.70 and later unless noted.

Button naming scheme for 64 pH and temperature channels

A component is any screen object, such as a button, a variable value display or a graph. From 5.70, DAQFactory auto-increments a trailing number when you duplicate a component. If the first button is named Button1, its duplicate becomes Button2, and so on. That trailing number is the key that ties each button to its channel without typing a channel name into 64 property sheets.

Pick one of two layouts before you duplicate anything:

Layout Button names (example) Channel lookup Trade-off
Single series Button1 to Button64 1 to 32 map to pH, 33 to 64 map to temperature (batch = n minus 32) One duplicate chain. Every script needs an offset branch.
Two series PHButton1 to PHButton32, TempButton1 to TempButton32 Trailing number equals the batch number directly Two duplicate chains. The prefix identifies the measurement type.

The two-series layout keeps the lookup arithmetic out of the shared script and is the better choice for a batch plant. Name the channels the same way, for example pH1 to pH32 and Temp1 to Temp32. Then the number suffix of the button always equals the number suffix of the channel.

Check: duplicate the first configured button once. The expected reading is that the new component carries the next integer suffix (PHButton2 after PHButton1) with all properties and event script copied. Do not duplicate further until this check passes.

Channel binding derived from the component name

Every button must display its own channel's live value, yet all 64 should carry identical configuration. Two 5.70 features make that possible:

  • User properties. These are custom properties you add to a component. Store the batch number or the full channel name in a user property on each button. You enter one value per button, and every script reads the property instead of a hard-coded name.
  • Name-derived lookup. Parse the trailing digits of the component's own name and build the channel name from them, such as prefix Channel plus the number. You make no per-button entry, so duplication alone completes the binding.

Handle the display side in the component's OnPaint event. There, set the component's display Expression to the channel that corresponds to the component name. Because the OnPaint script is identical on every button, one version of it serves all 64.

Check: run the page with live acquisition. Expected readings:

  1. Each button shows a value that matches the same channel viewed in the channel table. For example, PHButton7 matches pH7.
  2. Forcing or disconnecting one input changes only its own button.
  3. No two buttons show the same channel.

Shared click script held in a global string

When you need to change the button-press behaviour of all 64 buttons at once, hold that behaviour in one global string variable. Execute it from each button's event. Execute() runs a string as script, and it has two properties that make this pattern work:

  • Multi-line execution. One Execute() call runs any number of lines if each line ends with chr(10).
  • Caller context. The code runs in the scope of whatever calls Execute(). Private and local variables declared in the button's event are visible to the executed code, even though they did not exist in the global scope where the string was built.

Assign the string in a startup sequence so it exists before the operator touches the screen:

// Startup sequence: runs once at document load
global string MyGlobalString
MyGlobalString = "private x = 2" + chr(10)
MyGlobalString += "x++" + chr(10)
MyGlobalString += "? x" + chr(10)

Each button's event script then shrinks to two logical steps, identical on every button:

// Button event: same text on all 64 buttons
private string chanName = <channel name from user property or name suffix>
Execute(MyGlobalString)   // shared code can read chanName directly

In production, give the global string a descriptive name such as ButtonClickCode, because generic names collide with other globals. Editing the startup sequence and re-running it changes the behaviour of every button that calls the string. That is exactly what you want for a template, and exactly what surprises you later if you forget that the behaviour is shared.

An alternative that avoids string-built code is to add a component function to each button. That function calls a global sequence function and passes the component's name as the parameter. From 5.70 you can also use the messaging functionality to send a component the code it should execute. Both work, and both are still indirect.

Check: add a temporary ? chanName line to the shared string, re-run the startup sequence, and press three buttons. The expected reading is three distinct channel names in the command/alert output, each matching the button pressed. Remove the diagnostic line afterwards.

Per-channel routines driven by a channel-name parameter

A calibrate routine has to act on one channel chosen at run time. On 5.7x there is no pointer or reference to a channel object, so pass the channel name as a string. The routine then performs every channel-specific operation through Execute(). References were targeted for release 6.0, so check the release notes of your installed version before designing around their absence.

The calibration sequence has four phases, and each is a channel-specific operation:

  1. Change the channel's sampling rate to the calibration rate.
  2. Read the channel repeatedly and compute the calibration.
  3. Restore the normal sampling rate.
  4. Clear the channel history so pre-calibration data does not mix with calibrated data.

The speed penalty of Execute() comes mostly from the number of calls, not from the number of lines. One Execute() carrying five lines runs faster than five single-line Execute() calls. Build each phase as one concatenated string and execute it once:

// Global sequence function Calibrate(string chanName)
private string cmd = ""
cmd += <line using chanName: set calibration sampling rate> + chr(10)
cmd += <line using chanName: any other setup> + chr(10)
Execute(cmd)
// ... acquisition/read loop, one Execute per batch of reads ...
cmd = ""
cmd += <line using chanName: restore normal sampling rate> + chr(10)
cmd += <line using chanName: clear history> + chr(10)
Execute(cmd)

Read the exact property and function names for sampling rate and history clearing from the DAQFactory user guide for your version. Concatenate them with chanName exactly as the placeholders show.

Check: call the routine on one spare channel and watch it in the channel table. Expected readings:

  1. The update rate changes to the calibration rate during phase 2.
  2. The rate returns to its configured value after phase 3.
  3. The history is empty immediately after phase 4 and then refills at the normal rate.

Channel history snapshots from pass-by-value parameters

Pass by value means the function receives a copy of the argument, not the original. All DAQFactory function parameters are passed by value. When you pass a channel's history array to a sequence function, the function receives a copy of the history contents at the moment of the call. New readings that arrive while the function runs never appear in that copy. Changes the function makes to the array never reach the channel's history.

This decides how a routine that reads "a bunch of times" must be written:

  • One-shot analysis of existing data: passing the history array is fine. The snapshot is exactly what you want.
  • Waiting for new samples during calibration: passing the array is wrong. Re-read the live history by name inside the routine, through Execute() using chanName, each time you need current data.

Check: pass a channel history into a test function, wait several sample periods inside the function, and compare the element count of the parameter with the element count of the live channel. The expected reading is that the parameter count stays fixed while the live channel count grows.

Common styling through multi-select property edits

Colours, text format and similar appearance settings have no template either. Two practical routes exist:

  • Multi-select editing (5.7). Select all 64 buttons and change any common property in one edit. The event and function scripts count as common properties, so this also pushes a script revision to every selected button. The limitation is scope: you can only select components together if they are on the same page or on overlaid pages.
  • Script-driven appearance. Set colour and format from variables inside the shared OnPaint logic. One edit to the variable, or to the startup sequence that sets it, restyles every button regardless of page.

Use multi-select for one-off layout passes. Use script-driven appearance when styling must change at run time, for example to flag an out-of-range batch, or when the buttons span multiple non-overlaid pages.

The table below maps the symptoms these patterns produce to their causes:

Symptom Cause Correction
Edited one button, and its duplicates did not change Duplicates are independent copies, not template instances Multi-select edit, or move the logic into a shared string or sequence
Multi-select edit missed some buttons Buttons are on separate, non-overlaid pages Repeat per page, or drive the setting from a global variable
Changing the shared script altered every button unexpectedly Shared Execute() string or sequence is used by all buttons Branch inside the shared code on the channel name, or give that button its own event script
Calibration routine sees no new samples History was passed as a parameter, which makes it a snapshot Read the live history by name via Execute()
Per-channel routine runs slowly Many single-line Execute() calls Batch lines with chr(10) into one call per phase

Check: multi-select all buttons on one page and change the background colour. The expected reading is that every selected button changes and buttons on other non-overlaid pages remain unchanged.

Variables versus channels for high-rate indirection

If you need heavy indirection and speed, store data in variables instead of channels. On 5.70 and later (non-Express editions), functions exist to append values to a variable the way a channel accumulates history. Variable access is much faster than repeated Execute() against channels. The cost is that you must script services that channels provide automatically:

Capability Channel Variable with appended values
Access speed from script Slower through Execute() indirection Much faster
Polling Built-in timing Scripted polling loops required
Logging sets Supported Not supported
Export sets Supported Supported
Broadcasting Supported Not supported
Alarm checking Automatic Must be triggered manually
Edition requirement All Non-Express 5.70+

For 32 batches of pH and temperature that need audit logging and alarms, channels are the default choice. Accept the Execute() overhead and keep it small by batching calls.

Check: confirm that your edition and version expose the variable-append functions in the function reference before committing to the variable approach. If they are absent, the design stays channel-based.

End-to-end check of the 64-button screen

Run these checks in order with live acquisition on all 32 batches:

  1. Display binding. Compare each of the 64 buttons against the channel table. Expected: every button matches its own channel, and no value is duplicated or missing.
  2. Click routing. Press the first, a middle and the last button of each series. Expected: the graph shows the pressed channel's trace and the related work runs on that channel only.
  3. Shared-logic propagation. Edit one line of the global click string, re-run the startup sequence, and press any two buttons. Expected: both show the new behaviour without any edit to their event scripts.
  4. Calibration isolation. Calibrate one channel. Expected: only that channel's rate changes, then its rate returns to normal, and only its history is cleared. The other 63 channels are unaffected.
  5. Snapshot behaviour. Pass one history to a test function and compare element counts over time. Expected: the parameter count is fixed and the live count increases.
  6. Styling pass. Multi-select every button on the page and change one appearance property. Expected: all selected buttons update in a single edit.

FAQ

What happens if I edit one duplicated DAQFactory button?

Only that button changes. Duplication copies properties, user properties and component functions or events, but it creates no link between copies. Use multi-select editing on the same or overlaid pages, or move the logic into a shared global string or sequence.

What happens if I pass a channel history array to a DAQFactory sequence function?

The function receives a copy of the history as it stood at the moment of the call, because all parameters are passed by value. New samples do not appear in the copy, and changes to the copy do not affect the channel. Pass the channel name as a string and read the live history via Execute() instead.

What happens if I call Execute() once per line instead of batching?

The code runs noticeably slower. One Execute() carrying five lines separated by chr(10) is faster than five single-line calls. Group each phase of a routine into one concatenated string.

Can a DAQFactory 5.7 function take a channel by reference?

No. The 5.7 series has no pointers or references, and they were targeted for release 6.0. Pass the channel name as a string and build the channel operations with Execute(), or use variables with appended values on non-Express 5.70+ if you can give up logging sets, broadcasting and automatic alarm checking.

Back to blog