The Sunday Blog: Optimized coding

An example

Besides, among others, blogging about Nextion, administrating the Nextion user forums, (beta-)testing new hardware and software, my dear colleagues and myself monitor also some other Nextion-related groups and forums, always looking for situations where we can help users around the globe in realizing their Nextion based projects.

And so, one day, I stumbled over the following post in an internet forum:

HelloI wrote a weather forecast application based on an ESP32 and NX8084K050_011.In total 1172 images for 61 Icons:
46 animated weather icons
15 static weather iconsSerial commands are kept minimal by using variables:
First row: “vdsc0” … “vdsc6” for 7 images
Second row: “vhsc0” … “vhsc7” for 8 images
Values: -30 to +30 (negative: night, positive: day, zero: blank)
Only on changing page or changing icon will send new value (50-500/day)Two timer cycle in 75ms:
“tmdsc” for first row, responsible for 7 icons
“tmhsc” for second row, responsible for 8 iconsTimer “tmdsc” has 2873 lines of code
Timer “tmhsc” has 3278 lines of codeOn each timer event cycle as folowed:
Timer “tmdsc”: tmdsc
Timer “tmhsc”: tmhsc
I have tried add more(15 in total) timer but has no effect. Program Code has still the same line amount or more.Is it even possible to simplify my code? Less lines? Any suggestions?

I had bookmarked this interesting post with the intention to look deeper into this at a later moment, but only a few hours later, I got a message from my dear Canadian friend and colleague Patrick Martin“Did you see the guy that is looking to reduce his code from some 6151 lines of code … I can reduce to 21 lines” followed by a link towards a .txt file with his solution. That made me curious!

So, I looked closer into that user’s code and into Patrick’s solution. Finally, I decided to write this article to explain a few code optimizing techniques for the Nextion programming language. Unfortunately, the user did not publish his entire .hmi project file, so that we can’t come up here with the fixed ready-to-use solution, but there is enough to see in the user’s project description above, so that we might do more than enough with his linked timer event code listings.

Analyzing the project

As described above, it’s about a weather forecast application. An external MCU (here a ESP32) sets a few variables vdsc0 to vdsc6 for 7 days and vhsco0 to vhsc7 for 8 (hourly) time slices, always with a value from -30 to 30, over Serial. The timer routines running on the Nextion will then set the corresponding .pic resource (from a huge selection) to the corresponding Picture component pdsc0 to pdsc6 and phsc0 to phsc7. But, and that makes things a little more challenging, since there are animated sequences, there are additional variables as “frame loop counters”, for example vdscf45, intended to control a sequence of 45 animated pictures, increasing the picture index by 1 on each timer run. But once frame 34 reached, the image shall remain static until frame 44 as we can see in this code snippet from the original tmdsc timer event code:

  if(vdscf45.val>=34)
  {
    pdsc0.pic=p0.imgWeaSt.val+0+34
  }else
  {
    pdsc0.pic=p0.imgWeaSt.val+0+vdscf45.val
  }

This one applies if vsdc0 or its “sister” variables are from -30 to -25, from -16 to -11, from -9 to -4, from +4 to +9, from +11 to +16, and from +25 to +30. The only thing which changes for the different vdsc0 values is the picture index of the first animation frame (bold in the listing above).

For -24 and +24, there is a 30 frames animation, for -23 and +23 – 4 frames, for -22, -21, +21 and +22 – 35 frames, for -20 and +20 – 7 frames like in this snippet:

}else if(vdsc0.val==-24) // --------------------------------- Day 0 symbol -24
{
  pdsc0.pic=p0.imgWeaSt.val+210+vdscf30.val
}else if(vdsc0.val==-23) // --------------------------------- Day 0 symbol -23
{
  pdsc0.pic=p0.imgWeaSt.val+240+vdscf4.val
}else if(vdsc0.val==-22) // --------------------------------- Day 0 symbol -22
{
  pdsc0.pic=p0.imgWeaSt.val+244+vdscf35.val
}else if(vdsc0.val==-21) // --------------------------------- Day 0 symbol -21
{
  pdsc0.pic=p0.imgWeaSt.val+279+vdscf35.val
}else if(vdsc0.val==-20) // --------------------------------- Day 0 symbol -20
{
  pdsc0.pic=p0.imgWeaSt.val+314+vdscf7.val

Again, only the starting index of the animation (bold) changes and this time also the modulo counter for the animation (italic).

From -19 to -17 and from +17 to +19, -10 and +10, -3 to +3, there is a static picture:

}else if(vdsc0.val==-19) // --------------------------------- Day 0 symbol -19
{
  pdsc0.pic=p0.imgWeaSt.val+321
}else if(vdsc0.val==-18) // --------------------------------- Day 0 symbol -18
{
  pdsc0.pic=p0.imgWeaSt.val+322
}else if(vdsc0.val==-17) // --------------------------------- Day 0 symbol -17
{
  pdsc0.pic=p0.imgWeaSt.val+323

This code (or simplified versions of it, if there is a shorter animation, or an animation without pseudo-static end, or no animation at all) repeats in a long if() – else if() – else if() structure with one clause for every possible vdsc0 value from -30 to +30, thus 61 times. The whole sequence is then repeated 6 more times for the vdsc1 to vdsc6 variables. For the hourly variables vhsc0 to vhsc7, we find a similarly redundant structure in the tmhsc event code. So, in total, there are at least 14 variables times 61 possible values = 854 if() clauses to process. In reality, there are a few more since the 45 frames animations have an additional if() clause for the dynamic and the pseudo-static part.

And now to simplifying everything

As we can see, for all 14 variables and the corresponding picture components, the code is very similar, only the variable to check vdscn or vhscn and the target component pdscn or phscn, and sometimes the vdscfnn animation counter change. Arranging these in a way that these have consecutive object ids allows indirect addressing of both, using the p[array], allows already to divide the number of code lines by 15:

for(sys0=0;sys0<=14;sys0++) //Days and Hours index 
{
  //Process everything here, depending on the current variable:
  //...
  b[pdsc0.id+sys0].pic= //result of previous processing
}

Now, let’s look at this “Process everything here, depending on the current variable”:

First, we have to get the value of the variable vdscn or vhscn which corresponds to the current sys0 value and attribute it to sys1:

  sys1=b[vdsc0.id+sys0].val + 30

Then, we have to find the starting index of the corresponding animation or simply the resource id of the static picture. We use a lookup table in form of a string with comma-separated values and extract the selected numeric value into a tmp.txt variable before we can transform it back into a number using the cov function into sys2:

  spstr "0,35,70,105,140,175,210,240,244,279,314,321,322,323,324,359,394,429,464,499,534,535,570,605,640,675,710,745,746,747,748,749,750,751,752,787,822,857,892,962,963,998,1033,1068,1103,1138,323,322,321,314,279,244,240,210,175,140,105,70,35,0",tmp.txt,",",sys1
  cov tmp.txt,sys2,0 //offset

Now, we still need to know which animation counter’s value. Starting from the idea that the ids of vdscf45vdscf35vdscf30, vdscf7, vdscf4 variables are consecutive, and adding a vdscf0 for the static pictures which we initialize to 0 and never change it, we can set up a similar lookup table and retrieve the current frame index into sys3:

  spstr "0,0,0,0,0,0,2,4,1,1,3,5,5,5,0,0,0,0,0,0,5,0,0,0,0,0,0,5,5,5,5,5,5,5,0,0,0,0,0,0,5,0,0,0,0,0,0,5,5,5,3,1,1,4,2,0,0,0,0,0,0",tmp.txt,",",sys1    
  cov tmp.txt,sys3,0 //counter

Then, it’s time to handle the pseudo-static part of the 45 frames animations when the counter is > 34 and put the result into sys4:

  if(sys3==0&&vdscf45.val>34)
  {
    sys4=34
  }else
  {
    sys4=b[vdscf45.id+sys3].val // sequence
  }

And that’s the moment where we can set the picture resource id to the corresponding picture component:

  b[pdsc0.id+sys0].pic=p0.imgWeaSt.val+sys2+sys4

That was the whole inner processing thing of the for() loop.

Naturally, at the end, we’ll have to increase the animation counters and handle the cycling with the modulo operator. So, here the final result with its 21 instead of 6151:

for(sys0=0;sys0<=14;sys0++) //Days and Hours index 
{
  sys1=b[vdsc0.id+sys0].val + 30
  spstr "0,35,70,105,140,175,210,240,244,279,314,321,322,323,324,359,394,429,464,499,534,535,570,605,640,675,710,745,746,747,748,749,750,751,752,787,822,857,892,962,963,998,1033,1068,1103,1138,323,322,321,314,279,244,240,210,175,140,105,70,35,0",tmp.txt,",",sys1
  cov tmp.txt,sys2,0 //offset
  spstr "0,0,0,0,0,0,2,4,1,1,3,5,5,5,0,0,0,0,0,0,5,0,0,0,0,0,0,5,5,5,5,5,5,5,0,0,0,0,0,0,5,0,0,0,0,0,0,5,5,5,3,1,1,4,2,0,0,0,0,0,0",tmp.txt,",",sys1    
  cov tmp.txt,sys3,0 //counter
  if(sys3==0&&vdscf45.val>34)
  {
    sys4=34
  }else
  {
    sys4=b[vdscf45.id+sys3].val //sequence
  }
  b[pdsc0.id+sys0].pic=p0.imgWeaSt.val+sys2+sys4 //result of previous processing
}
vdscf45.val+=1%45
vdscf35.val+=1%35
vdscf30.val+=1%30
vdscf7.val+=1%7
vdscf4.val+=1%4

21 lines! Thank you very much, dear Patrick, for this hyper-efficient optimization!

Questions? Suggestions? Critics? Join us in the Nextion forums!

Happy Nextionning!