Pointer table, IO, and subroutines

So…I have another question. I’m trying to access my I/O from my subroutine. Reading through things it seems like the best way to do this is to assign all that I/O to pointer tables. There are 8 outputs, 2x8 inputs. I have a loop telling it to grab one index on each iteration of the loop and turn on that valve.

It looks like there is no direct way to turn on or off one of my outputs in the pointer table unless I first move that pointer table element to a pointer variable. Is that correct?

Like this:

It would be great to just be able to pass the value of 1 or on directly to the pointer table element, but this is not possible correct?

I thought it would be easiest to just point to everything in a table and then access each individual table element as needed, but if these are the extra steps that are needed maybe there is a better way to do this?

You are correct that you cannot modify the value referenced by a pointer table - you must assign the pointer to a pointer variable first. I’m not sure why this limitation exists, but it does - even in OptoScript.

However, you don’t need to assign the pointer back to the table, so the last command in your block isn’t doing anything as the table already contains that reference, so that will simply things a bit. Remember that a pointer is merely a reference to something else so when you copied the “reference” from the table to the pointer variable they are both “pointing” to the same spot (a digital output in this case).

PointerVarA = PointerTable[0];
PointerVarB = PointerTable[0];

TurnOn(*PointerVarA); //This will turn on what PointerVarA is pointing to
TurnOff(*PointerVarB);  //This will turn off what PointerVarB is pointing to

MyInteger = *PointerVarA; //Get the value of what PointerVarA is pointing to
//MyInteger is 0 since PointerVarA and PointerVarB point to the same thing and the last command turned it off.

Essentially, when you assign a “tag name” (this could be a IO point, a variable, almost anything in Pac Control) to a pointer table index, you are providing the table with the name of an entity like a var or IO point. Whenever you loop in your chart, you “load” the Pointer from the pointer table. The idea being that this iteration of the loop is the next index number of a set of whatever it is you’re trying to manipulate.
I’m not saying you can’t use pointers in other ways, but the primary use of pointers is to have a process that needs to happen over and over but to a whole list of say equipment items.
Lets take a pump control for 50 pumps, all with essentially a similar control scheme. I would assign a pointer to:
Run_Pump
Pump_Status
Pump_Pressure
Pump_Current
and so on until you have essentially every variable you might need to control and monitor that pump. Then you load all these tag names into the pointer table (before you enter the loop) and from that point on, you load each pointer from each associated pointer table (one pointer and one pTable for each variable or control element) using the index number of the loop. This way, everytime your loop iterates, the index changes, and so does all the elements you are now pointing to for that loop iteration.
Now you have one code group that when perfected, does all 50 pump control processes. If you have to fix something, you are fixing all 50 pump logics.
You can also make minor differences inside the loop simply by making an exception based on index number of the loop. Therefore, if this one pump control number 29 needs to have something different, you simply look for index 29, and branch for that exception. It could be a big difference or small, just create a separate branch that handles only that difference.
There are 3 things to remember, when loading the pointer table you need the “&” such as
PumpOn_pTable[0] = &Pump_On_din; in order to load the ptable. In order to reference the pointer or I.E.; use that pointer in the logic as a control element, you need this *Pump_On_ptr. And finally, when you load the pointer, you don’t need anything, just load it such as Pump_On_ptr = PumpOn_pTable[Loop_Index];

Thank you both for your help on this!

Phillip - that was exactly what I needed to know about. I wasn’t sure if I needed that last command or not. I’m sure that OptoMary or Ben may be able to tell us why I couldn’t just directly set PointerTable[0] to a number or do TurnOn(PointerTable[0]); directly. That would definitely simplify it. I didn’t know I wouldn’t be able to so when I first started this I was wondering why nothing was working. Then I looked around and found that you had to extract it into a pointer variable to be able to use it.

Barrett, Thanks for all that great info. It sounds about like what I’m doing. I didn’t use an init file to load my pointer tables. I just used a OptoScript Block. I only have 2 LeachFields with 8 lines each and the possibility or a third in the future so it’s not too much to do load. It all loads before I go into the loop. I set that up with pointer tables pointing to my I/0. I have 8 valves, 8 flow sensors, and 8 high level floats that are each in their own table and then they are passed into my subroutine which runs each line. In the subroutine is where I was breaking them out into pointer variables before using them, but then for an output I thought I would need to “put it back” into the pointer table.

I was interested in your exception code for an individual index. How do you usually go about adding that exception.

I also have another Forum question that OptoMary answered to if you want to see a little more of what I’m trying to accomplish. Break out of a subroutine?

Thanks!

My guess: The pointer table can hold pointers to many different types of variables/points/tags and the controller is unable to figure out the data type the pointer points to at runtime - so you are required to assign the pointer to a pointer variable of a specific type so the controller will know what it is dealing with.

“I didn’t use an init file to load my pointer tables.”

I did not mean a file, I meant a script block that has initialization script in it such as loading the pTables. You only load pTable once on power up, then you simply load the pointers from the ptables from that point on.
Most programmers tend to over think things and I’m no exception. If I were you, I would stay away from subroutines, as they tend to add to the complexity of a strategy. They do have useful purposes, but I rarely use them and I do fairly complex projects.
In your case, a wastewater project is easily done doing everything in a single chart. I would take all the script you have in the subroutines, and simply bring it into a main chart.
The exception I am referring to is where in the main logic flow, you use a conditional to check if this is loop 29, if yes, then branch and do that logic that is different, then return to the main logic flow.
If you do pointers where you do not always have a tag name loaded in the pTable, then when you load the pointer, you check if the table value is “Null”, if so, you ignore that load statement. That way, you will not get a NULL Pointer error.

Sounds good.

I understand the complexity issue as I’ve been running into it. I was just trying to make the program scalable since we are most likely going to add in the future. I thought this way all I would have to do is change some calls and add a couple more tables for each new leach field.

I wasn’t sure if you meant init tables. I just know that’s what they recommend in the 1700 manual.

That sounds about right, philip, thanks!

Hello Opto Awesomes!
I am reviving this topic because I found it very helpful.
Philip- your excel sheet for generating tag names was instrumental in my inherited project. Thank you. And OptoMary, pointers turned my 15+ page strategy to one page. And easy to add additional chambers and debug.
My question is, on strategies like this (where pointer tables are loaded on the power up chart) how does one account for redundant strategies with redundant controllers? I heard something about a file that can be downloaded after the strategy to the controller? er something? What is happening is that when I simulate a catastrophic controller failure, the strategy does pick up where it left off, but the pointers have not been defined because the power up chart did not run first. Am I missing something?

thank you,
LadyBNC

I haven’t worked with redundant controllers before, but this would be my approach:

Perform your pointer table initialization in a subroutine. Call the subroutine in your power up chart. Check for null values in the pointer table after every sync block and call the subroutine then if needed.

Philip,
perfect. Ill give that a shot. thanks!

I was just thinking that using a subroutine may be difficult with passing in all the values you need. It may be better to put the initialization in its own chart and then use the call chart command.

If you have a moment, I would really like to understand what and how you are simulating that causes a controller burp such that the power up chart does not run after it recovers.

EDIT. Never mind, I I think I see now with the redundant controllers how one may fail and not run the power up chart in the other.

IT WORKS!

can not ‘call chart’ in a redundant strategy :frowning:

What I did: there is only one sync block on this chart, and it is after the completion of the loop. When it reaches the end of the table and resets to zero it initializes the unchanging pointer tables. So I only loose about a minute of data before everything kicks in again. BEAUTIFUL. Your suggestion was perfect.
thanks Philip!

With redundant controller set up there are 2 controllers and an arbiter. One controllers is set as active and the other as back up. The strategy is on both controllers and if one fails the other should pick up right where the other left off.

With PAC Redundancy manager, you see all three units, the arbiter, the primary controller and the Qualified back up. Next to the back up controller there is a “make active” link that allows you to switch active controllers. Click this link and see if the strategy is still running after the switch. The power up chart does not run again, it simply continues like it is the first controller.

Next is the physical simulation at the controller itself…where I disconnect the power to controller #1. :slightly_smiling_face: