Initializing & Using Tables


#1

An interesting question from a customer came in that went something like this (I love that he gave lots of details/background so I easily get the big picture, and in this case, go in a different direction than his initial question).

At what point does a string table set as “persistent” lose its data? … I have a multi-language interface [using] two string tables stEnglish and stPORTUGESE [and] a numeric variable called nLANGUAGE… I use a button to toggle the bit in nLANGUAGE from 0 to 1.

I then have alternating visible labels which each look at the respecting table and become visible based on the bit. So initially, ENGLISH shows, then once the button is pushed the toggle sets the language to PORTUGESE. All of this works great. (if there’s a better way to toggle text from engine, PLEASE let me know)…

Should I initialize the table via hard code?

To answer his final question first, I’d say: “yes, hard-code those tables.” This is based on an assumption that he won’t need to be changing these table values very often or on-the fly. (Of course, with PAC Project you have many options for initializing and/or changing variables.)

Assuming he takes this suggestion, he won’t need to use persistent memory – which gets wiped out (as documented in form 1700, chapter 9) when:

  1. A strategy with a different name is downloaded.
  2. The RAM memory on the controller is cleared.
  3. A new firmware kernel is downloaded to the controller.
  4. The persistent object is changed (modified table length/width, etc.)

He also asked if there was a “better way to toggle text.” I’d suggest have PAC Display (or even groov) use just one string table, which gets swapped out if/when the language is changed.


Why would I do it this way? Two main reasons:

  1. Easier to build your HMI – time is money, let your PAC do the heavy lifting and keep your HMI simple.
  2. Today you support 2 languages, hopefully your product will be in-demand world-wide and you’ll want to support 20 (or 200!) languages in the future.

That’s my short answer, a couple other considerations to follow…

-OptoMary


PAC Control 101: Why/when/how would I use a TABLE?
#2

Worried about speed? In the example above, I wasn’t sure how fast the “Swap languages” block would take to run. If it was 5 seconds that’d be too slow and I’d need to come up with another method.

Luckily, built right into PAC Control debugger is this indicator that shows how long it took a block to run. (Keep in mind that the debugger itself uses resources on the PAC so when this code is running WITHOUT the debugger looking at it, the code will run even faster.)


Here’s what to do: set a breakpoint on the block you’re wondering about, step over it and check the time. Ta da! Just a fraction of a second here. That’s fine for this application.

What if it’s not? There are always other options. A different PAC (SoftPAC would be super fast for this), another method, and also the considerations mentioned in form 1776 - Optimizing PAC Project System Performance Technical Note (including why that Delay in the loop is so important).

FYI, in the image shown above, I moved 500 elements at once on a SNAP-PAC-R1.

While we’re on the topic of speed/efficiency and tables, I have a couple of suggestions when you initialize those tables…


#3

How can I easily initialize a giant table?

Here are a couple tricks I use to automate the process of writing repetitive code, and reduce the odds of human error. I like to use a free download called Notepad++. (Thanks, Ben, for telling me about that!) In particular, I love the built-in macro feature to record then play keystrokes.

Let’s suppose I want to initialize a numeric table. I might do it this, perhaps doing a massive copy/paste then changing the indices and values:

[INDENT]ftFakePIDdata[0] = 0.5;
ftFakePIDdata[1] = 1;
ftFakePIDdata[2] = 2;
ftFakePIDdata[3] = 3;
ftFakePIDdata[3] = 6;
ftFakePIDdata[4] = 12;
ftFakePIDdata[5] = 17;
[/INDENT]
But what if I have 500 elements? Or what if I’m human and make an error, like having two [3]s in there? (Did you notice that?) Could be hard to track/fix.

Better to automate the process, including indexing, like this:
[INDENT]nPIDIndex = 0;
ftFakePIDdata[nPIDIndex] = 0.5; IncrementVariable(nPIDIndex);
ftFakePIDdata[nPIDIndex] = 1; IncrementVariable(nPIDIndex);
ftFakePIDdata[nPIDIndex] = 2; IncrementVariable(nPIDIndex);
ftFakePIDdata[nPIDIndex] = 3; IncrementVariable(nPIDIndex);
ftFakePIDdata[nPIDIndex] = 6; IncrementVariable(nPIDIndex);
ftFakePIDdata[nPIDIndex] = 12; IncrementVariable(nPIDIndex);
ftFakePIDdata[nPIDIndex] = 17; IncrementVariable(nPIDIndex);[/INDENT]

(Notice this is good for letting you insert an element somewhere other than the end w/out having to re-do the index values.) Take this a step further and easily automate even more by creating a macro to add the code around your individual data values, then the Notepad++ “play multiple” feature:

//youtu.be/eev7VzX1w2g

Handy for this and much more, get the (free to download) Notepad++ here: http://notepad-plus-plus.org/

Code on!

-OptoMary


#4

Hey all you table users out there,

I just came across this code and realized I’ve neglected to mention a couple of commands that should be your friend.

Here’s the code I saw (and was tediously stepping through in PAC Control’s debugger, on my way to the code I REALLY wanted to debug):

// Initialize the string tables 
for i=0 to 99 step 1
  DstStrTblHdr[i]   = "";
  DstStrTblBody[i]  = "";
next

Here’s the better way to initialize those two string tables:


// Initialize the string tables 
MoveToStrTableElements("", 0, -1, DstStrTblHdr);
MoveToStrTableElements("", 0, -1, DstStrTblBody);

Note: check out the “-1” in there: a handy shortcut for this an other table commands where you can skip the “End Index” and just tell the command: “do the whole table.” Neat-o!

Not only is this code easier to step over than the original, but it’s also easier to maintain because it’ll work even when you change the size of your table. (Notice in the original code, if your table went from 100 elements to 200, you might have a hard-to-find bug.)

There’s also a similar command for numeric tables, like this:


// Initialize the numeric table
MoveToNumTableElements(22, 0, -1, ntMyFavoriteNumbers);

That’s how I like to “set the table”! Har. Bad table pun. Couldn’t resist.


-OptoMary


#5

Since this thread came back to life, it seemed like a good place to toss in another idea for the original OP’s question. The approach Mary outlined has each Pac Display button getting its text from an element in a string table, which is itself defined by a loop that transfers in text from a string table filled with the chosen language. She showed that even if the table has 500 elements, it still populates very quickly.

However, a scalable approach that might be even faster would be to use pointers. Here is what the chart might look like, showing the variables used in it:


The initialize optoscript block just populates all of the tables. Each string table with a different language gets pointed to by an element of the “Button_Language” pointer table.


Somewhere in PAC Display, if a new language needs to be chosen, the nIndex_Language_New value gets set to the corresponding index. The conditional block looks for the change to occur.

Once the change in index is detected, the corresponding element of the pointer table gets transferred to the string table pointer that each button is looking to for its text.

The “Text in from Control Engine” dynamic attribute of those PAC Display buttons would look something like this, with each button looking at its assigned element in the string table pointed to by pstButton_Labels:


#6

Ah-ha! Excellent point. You are approaching Black Belt OptoExpert status for your deft use of pointers and even pointer tables.

Now, you force me to point out a couple of limitations in our HMIs which I work around by, in this case, re-loading one string table (which is not-so-efficient, as you noticed).

I don’t actually recall if the HMI was specified as [I]groov[/I], or if I just picked that because it’s nifty and fun. Unfortunately, [I]groov[/I] doesn’t currently support pointers or timers. So, that forced my hand… lucky the copy was pretty fast.

Fortunately, the more mature product PAC Display is cool with pointers, at least, enough that your implementation would be lovely and faster as you mention!

I would love PAC Display (and the OptoScript compiler) even more they could do a double-derefence for us. In other words, if you could skip needed the pstButton_Labels pointer and just have PAC Display directly get the string from something like:

*(pstButton_Language[nIndex_Language_New])[2]

Even better if that 2 could also be a variable. Now were getting into some really crazy pointer stuff. :slight_smile:

But I digress.

I should mention, though, the trade-offs we make in these kinds of choices. For example, here I avoided pointers because [I]groov [/I]doesn’t do pointers yet. But in another situation I’d make that same kind of trade-off on speed (if it’s not too much of a hit) vs. “can the next guy understand it.”

Let me explain that.

Obviously, you are perfectly content and happy to use pointers and pointer tables and that’s excellent. Not everyone is so comfortable (especially on a Friday afternoon) and might get a headache trying to understand all that. We have many non-programmers who love our stuff because it’s easy to program without any heavy-duty programming experience or special knowledge. They can do all their logic without ever using a pointer or a subroutine and they like it that way.

Let me give another example of a style/efficiency choice–a pet peeve of mine, actually. While in OptoScript, you could write code that looks like this, and it might even make sense to some people who think this way:


 
doOutsideLights = not (bDayTime);

I would much rather see this (or at least a comment explaining what that one line above is supposed to do):


 
if (IsVariableTrue( bDayTime )) then // if it's daytime, we don't need outside lights

  TurnOff( doOutsideLights ); 

else // it's night time, let's turn on those outside lights

  TurnOn( doOutsideLights );

endif 

Is the first way a little faster for the PAC to process? Probably. Does it matter with today’s processor speeds, especially for a lighting application? Unlikely.

Is the second way easier for my brain to comprehend? Definitely. And I need to save as many of those CPU cycles as possible!

Anyway, thanks for pointing out the pointer thing. You make a pointedly good point. HAR! Okay, I’ll stop now.

-OptoMary