AN EXAMPLE: The soon-to-be-expanding Bear Family’s control system
(See the bottom of this post for the sample strategy referenced here.)

For those not sure about the what/why/when of pointers, let me give a hypothetical example. Suppose Goldilocks is the friendly neighborhood controls engineer, and she likes to make sure Mr. and Mrs. Bear always have porridge that’s neither too hot nor too cold. She might write a control flowchart that looks like this:

This is all well and good until one day the Bears announce they’re expanding, and will need a bigger porridge monitor/control system. They ask Goldilocks how difficult it would be to add another heater, fan, and temperature probe to the system.

Because she’s a smart controls engineer, and knows that most bear litters yield two or three or even as many as six baby bears (according to www.bear.org, anyway), she figures she better make some changes to this logic so it’s easy to expand in the future, since she and the Bears might be especially busy once that litter arrives.

THE BASIC IDEA:

Rather than just doing a copy/paste like she did from Mama’s logic when she went to create Papa’s logic, she considers using tables and maybe even pointers to make her logic more generic, and easy to expand.

The Setup / Monitor Part:

She wants to just have those two conditions to check, which she’ll check for this bear, then the next bear, then down the line until the end and back to the first bear. Sounds like she’ll need a list (or lists) of bear attributes to ponder so the final flowchart might look more like this:

That sounds simple enough (notice Goldilocks likes to start with this “high-level design” where she’s got her overall logic all laid out). Now she’ll figure out what to put in those blocks and work backwards from there.

She might even want to make some upgrades for the future, in case Mama Bear decides later she wants to adjust what “too hot” or “too cold” means on a bear-by-bear basis, perhaps from her Smartphone via groov!

Looping through Bears

INTERNALLY, we’ll refer to each bear by a number or index, starting with Mama Bear as 0 (since that’s where computers and PAC Control tables start counting), for a total of MAX_TOTAL_BEARS (hoping 10 will be enough for now). This MAX_TOTAL_BEARS variable will be used in the new Condition block, when we’re deciding if we’ve finished with all the bears and it’s time to loop back around.)

But for our EXTERNAL users, like Mama Bear with her smart phone, we’ll want to store more human-readable labels for each. So let’s create and load that table, for starters:

Now let’s look at the new logic we added, and how we’ll keep track of where we are in the bear line-up. First we’ll increment the nCurrentBearNumber.

Now, here’s where it starts to get a little trickier, and we might want a pointer or pointer table, kind of like we use in this HIGHLY RECOMMENDED BY PRODUCT SUPPORT chart (which re-enables/connects your I/O Units if/when they get disconnected from your controller).

How do we change that temperature logic for “Too Hot?’ and “Too Cold?” to use the bear number instead of my current aiMamaTemp and 100.0 Literal?

We can use a float table for each set of values, one for “too hot” and one for “too cold.” I’ll call them: ftTooHotTemperatures and ftTooColdTemperatures. But what about that analog input?

That’s where it would be handy to have a table of temperature inputs, and a pointer to the current bear porridge temperature. That way, our “Too Hot?” condition block becomes:

The “Too Cold?” condition block would be very similar:

At the beginning of the flowchart, initializing that table of temperatures and the pointer to the current temperature looks like this:

Note: for that last “initialize the current bear” assignment/command, I could’ve just assigned the pointer: paiCurrentBearTemperature to aiMamaTemp, but initializing it from the table is more generic and easier to maintain. If someone ever moves Mama Bear out of the 0 slot, our loop will still start with bear 0.

To move that temperature pointer to the next one in the list shown above, we’ll just use a command like this just before we do our delay and loop back around to the next bear:

The Control Part:

So that was the monitor part (where we went from looking at just the aiMamaTemp and aiPapaTemp to a whole list of analog inputs).

For the control part, we currently have 2 sets of two digital outputs (one for the fan and one for the heater). The idea is the same for those, I’ll just load up 2 more tables—one with a list of fan switches and one with a list of heater switches.

Then the “Turn on Fan” block goes from this command to turn on a particular output:

To this more generic command which will turn on the appropriate output for the current bear:

Ta-da! That’s it! Now that you’ve changed your logic to loop through and do the same monitor/control steps for EACH bear, expanding in the future will be super easy. Just add the I/O points, and initialize them in your pointer table and you’re controlling an even bigger part of the world!

Really?

Ok, this particular example was a bit contrived. I’m sure all you real-world control engineers will have suggestions for more realistic ways to solve similar problems, yes? Let’s hear from you! Questions? Comments? Do share…

A pointer variable, rather than holding a value like other variables, instead holds the address of the variable it points to, so the pointer can be re-directed to point to another variable. See this Wikipedia link for a nice picture and way more detail than you want (including syntax examples similar to OptoScript).

That means when you’re stepping through your logic in PAC Control’s debugger, and you inspect a pointer, the pointer variable holds a name (location/address in control engine memory). You’ll click the button under that name to inspect that “Item Pointed To:”

In Action and Condition blocks, PAC Control’s compiler (“config mode”) does this extra “go get the real value” for you. It knows if you mean the pointer, or the value of the thing it points to, from context.

However in OptoScript, there are two special characters you’ll need, the star:[INDENT] *
[/INDENT]and ampersand:[INDENT] &
[/INDENT](for full details, see form 1700, the PAC Control User’s Guide, Chapter 11: Using OptoScript > OptoScript Data Types and Variables > Working with Pointers).

Here’s how I was taught to remember when to use which one:

Think of that star as what you’d see if you looked at the back end of a dart or arrow quiver, like if you were pointing it at the thing we were about to shoot. This is how you tell OptoScript you want that thing this points to, rather than the pointer itself.

So if I wanted the current value of the temperature I’m looking at, I could put it in a float called fCurrentTemperature by making this assignment:[INDENT] fCurrentTemperature = *paiCurrentBearTemperature;
[/INDENT]If I wanted to go the other direction, and set paiCurrentBearTemperature to point to fCurrentTempature, I’d use this syntax:[INDENT] paiCurrentBearTemperature = &fCurrentTemperature;
[/INDENT](Think of the & or Ampersand as representing “Address” so that command says: store the Address of fCurrentTemperature in paiCurrentBearTemperature.

In the above example, since we want a whole list of bear temperatures to loop through, and all those will be coming from Analog Inputs, we initialize a pointer table in a similar manner like this:[INDENT] ptaiPorridgeTemps[0] = &aiMamaTemp;
ptaiPorridgeTemps[1] = &aiPapaTemp;
ptaiPorridgeTemps[2] = &aiBabyTemp1;
…
[/INDENT]In general, if you’re getting a compile error in your OptoScript block on a pointer assignment, you’re probably missing a * or a &.

Remember, you don’t have to use OptoScript if you’d rather not remember all this syntax. You can do everything in non-OptoScript blocks that you can do in OptoScript blocks.

Not so crazy idea, I wish we had a better answer!
This is not EXACTLY what you’re looking for (and if you change the names of things, this will break), but there is one option of using GetPointerFromName, as discussed in this post:

Does Optoscript have something equivalent to an Eval() function? I haven't been able to find anything.
For those unfamiliar, Eval lets you pass expressions or operators in a string, and act on them.
As an example, the old project I've taken over makes use of very generically named tables, Digi0, Digi1, Digi2, etc. Dozens upon dozens of them, and they have the same set of operations performed on them.
Currently it goes something like this:
Digi0[23] = 4;
Digi1[23] = 4;
...
Digi45[23] = 4…

Pointers, like porridge, can definitely get messy, but in a lot of projects with large numbers of similar devices they pretty much become necessary. As you figure out what pointers are doing what, be sure to add some comments for yourself and the next guy or gal.

When I have a loop of code using pointer tables (for IO) and numeric tables (for setpoints), I typically have a single Optoscript block at the top of the loop/chart that assigns all the pointer table pointers back to individual pointers and the numeric tables to individual floats. I then use those values in the logic for the chart as if I’m working with a single device. In fact, you could write the code as if there is only one system being controlled, and then come back and put a loop around it and replace the controlled points and variables with tables. I think Mary’s example shows this quite well.

I also initialize all the pointers in the power up chart using multiple blocks for logical separation if needed. You could also do this at the top of the controlling chart like Mary’s example, I just prefer to have all my one-time initialization in the power up chart.

To build the repetitive initialization code as you are trying to do above, my go-to tool is Microsoft Excel. It helps to be familiar with how formulas work in excel though.

Here is an example of what an excel sheet with the above initialization could look like:

The $ in the formula tells excel to not change the value to the right when you copy and paste, so the formula can be written once and then copied across the entire sheet. So when the B$4 is copied down it will still be B$4, but when copied right, it becomes C$4. The & is the excel string concatenation operator.

The formulas that are built can then be copied and pasted directly into an Optoscript block.

The Excel example is great! I can certainly use it in the future. Thank you.

Your answer regarding pointers is quite true, much as I despise them they are necessary. With only 78 points of I/O throughout this entire application (and little expansion potential in the future) I felt that copious pointers weren’t entirely necessary. I have 25 analog points, the rest digital. Come to think of it this is my largest system in terms of I/O points. lol

Hi Looking at project with snap PAC and Groov I am trying to get my head around a project concept.
I have multiple machines all the same that I would like to control with one groov box. I understand that I should be able to do one chart and then use pointers to differentiate between machines. I did a little experiment but I can not see the pointers in groov. Do I need to do the logic using the common pointers and then for each machine have unique variables which will show up in groov?

Yes.
At this time, not everything in PAC/EPIC strategies shows up in groov (Pointers and PIDs for example).
So you need to do exactly as you said, just have a small handful of variables that you need for your groov gadgets and move the pointers to them in a chart periodically.

For groov and PAC Display I setup “display” variables for the devices that are repeated multiple times. This allows me to specify a display index that I can change from the HMI that then populates the display variables with device I want to view. This saves A LOT of time when building your HMI screens, as you only have to build one screen for all your devices.

Unfortunately this only works well with a single end user. If you need multiple users to use the HMI at the same time then you would need to either create pages for each device (a lot of extra mind-numbing work) or have some other work around (like creating display variables for each HMI instance - hard to make work in groov though).

It sure would be nice if we had a way to bind table indexes to a local HMI variable in the Opto HMIs to get around this limitation.