BiggP Posted March 18, 2022 Share Posted March 18, 2022 Hi, I am setting up a shop floor data capture from 12 robot wekding cells just to monitor production and I have come across a problem with the execute() function that have got me baffled. For each weld cell, there are 3 test channels to retain information - the part number being assembled (PARTCELL1 to 12) although this is just a number in a list as they are strings, the number of parts per cycle (PPC1 to 12) and the production target number (CELLTARGET1 to 12). The production target can be changed so for retaining the changes there are also channels TARGET1 to 24 (there are 24 parts in production). Using 2 dropdowns cellpick and partpick, the parts being assembled can be changed in any cell, which changes the prodution target also. For displaying data there are global variables corresponding to the output of the dropdowns (partlist, targetlist, ppclist) using arrays and 2 buttons for changing the prodution target and setting the part to a cell. This is where things start getting a little screwy, the set to cell button runs a sequence that executes two commands, I have tried seperating them to 2 sequences, assigning them to string variables before executing etc etc but I still get the same problem, the picture below will explain: Further to this, ?PARTCELL1=9, but if I ran the first command again then the output of both ?CELLTARGET1 and ?PARTCELL1 would be 200. I can't prevent one from changing the other, no matter how I try, please help Quote Link to comment Share on other sites More sharing options...
BiggP Posted March 18, 2022 Author Share Posted March 18, 2022 Please bear in mind this is a work in progress, but I thought some screenshots might help you understand what I am doing as a whole Quote Link to comment Share on other sites More sharing options...
AzeoTech Posted March 18, 2022 Share Posted March 18, 2022 Are partcell1 and celltarget1 both Test D to A channels? And do they have the same D#, I/O type and Channel #? If so, then they would the same name for identical channels and thus change in tandem. To fix, just make sure you have different channel #'s on all your test channels. A few random things: 1) your screens look great. The graphics are very sharp. 2) why are you using Test channels? Have you considered just using global variables? The only advantage of a channel I can think of would be leveraging Persist, but you can do something similar with a variable, either by using a register variable or by writing some script to persist/restore your settings. The advantage of using a variable would be that you could use an array, "partcell" for example, which would be indexed from 0 to 11 for the twelve options. Then you wouldn't need execute(), which is slower and not as easy to read 3) you don't really need all those doubleToStr() calls. DAQFactory will automatically convert. So: "partcell" + 1 + "[0]" will automatically convert 1 to "1" and return "partcell1[0]" because the first item is a string. The conversion doesn't work the other way though: 3+"4" will return an error, not 34, nor 7. However: "" + 3 + "4" will return "34". Quote Link to comment Share on other sites More sharing options...
BiggP Posted March 23, 2022 Author Share Posted March 23, 2022 Hi, Thanks for your suggested solutions, it was the channel numbering on the test channels, which I am using for keeping hold of data. I didn't want to use registry variables as we don't have the PC it will run on yet. I appreciate the tips regarding execute and doubletostr. I have dropped using doubletostr, but kept using execute (and now evaluate also) as I am not a well versed programmer and now I have all but finished the main display and it has a page refresh of 35-37ms I am happy with that. Especially as that is on my decrepid FX-8320 and it will be moving soon to a nice shiny new ASUS PN-51 mini PC with Ryzen 5 5300U CPU which has a 62% improvement for single threaded performance and ability to extend a desktop over 4x 4k displays (with the aim of creating a single page extended over 2 displays for 12 weld cells). The main part of the app that was unfinished before was the cell history (under label 'LAST 60 MINS' on screenshot) this is done now and for a non-programmer I think I have done ok, each cell has 6 variable values named tenscellx where x=cell number and are an array of 6 values. Every ten mins a sequence is started that adds CELLx[0]-CELLx[600] to start of array and it loses the last value. Then it compares the production target for each cell /48 (ten mins) to tenscellx[0] to [5] where x is 1 to 12 and if it is more than the production target it displays green, otherwise it displays red. This was a vast improvement over crimson 3.0 (red lion) which was 100+ lines of code and a lot more settings. All I had to do was set a name for each component grouping the first value, second value etc in all cells, then use the code after the picture in the sequence. It works differently to the threshold settings (it is more a conditional colouring) and i noticed on the forums a lot of requests for something like this, so maybe it will help someone else. The code is after the picture //setting colour for cell history (TENCELL1=0, TENCELL2=3, TENCELL3=0 they are all in group TENCELLS) global string cellgroup = {"TENCELLS","TWENTYCELLS","THIRTYCELLS","FORTYCELLS","FIFTYCELLS","SIXTYCELLS"} global string celltens = {"TENCELL","TWENTYCELL","THIRTYCELL","FORTYCELL","FIFTYCELL","SIXTYCELL"} global thiscell=0 global allcells=1 global string cellcomp //while loop cells 1 to 12 while(allcells<13) //while loop tenscell(allcells)[0] to [5] The variables in use are tenscell1={0,0,0,0,0,0} tenscell2={3,7,7,8,7,8} and so on, the iif sets the RGB code depending if it is higher or lower than the target and the execute sets the colour with component naming. while(thiscell<6) cellcomp = celltens[thiscell]+allcells global string strfc = iif(evaluate("tenscell"+allcells+"["+thiscell+"]<TARGET"+allcells+"/48"),"RGB(255,0,0)","RGB(0,255,0)") execute("component."+cellgroup[thiscell]+"."+cellcomp+".forecolor="+strfc) thiscell++ endwhile thiscell = 0 allcells++ endwhile I hope this can help if someone needs this type of colouring Quote Link to comment Share on other sites More sharing options...
AzeoTech Posted March 23, 2022 Share Posted March 23, 2022 Looks good. A few comments: 1) watch your indentation. 3 spaces in for all blocks (while, if, etc.) and 3 spaces back for the end of the block (endif, endwhile, etc) 2) if components are named the same you can actually set the same variable on each in one command. This doesn't work for member functions of the components, but it does for variables. So if you have three components named MyComponent, you can do: component.myComponent.forecolor = rgb(255,0,0) and it will set the forecolor on all three components. 3) an alternate, and often better method is to have the component set its own color through its OnPaint event, but in your case I'd go with what is working! 4) finally, I'm not sure DAQFactory is going to render across multiple displays in 4k. I think it will cut off at one display. You might need to instead create a modeless popup or popups, positioned on the 2nd monitor. Quote Link to comment Share on other sites More sharing options...
BiggP Posted March 24, 2022 Author Share Posted March 24, 2022 Thanks for the comments: 1) I try 2) This was quite a trial for me to complete, but in this case, because each part assembled has a different production target (there are 24 parts, and half of them can be made on multiple cells) then I needed to keep them independantly coloured. I assumed that the colour would not move through the array with the variable so felt as it is only 72 vatiables to assign colour to then to do them all every time was easiest for me 3) I did look at using an event, but couldn't find OnChange for variable values! I now realiuse that it is OnPaint. Same reasons as (2), which I suppose leads me to ask, if tenscellx[0] is assigned a colour, when it moves to next position in array ten minutes later, if that position had no colour specified, would it retain the colour it has been given, or would it default to black? 4) When I find out, I'll share the info. Quote Link to comment Share on other sites More sharing options...
BiggP Posted March 30, 2022 Author Share Posted March 30, 2022 Update: I had to make a change to the display, so that the main counter for each cell was green if on target, orange if 90% on target and red for any less. like below: I remembered what you said about using the 'onpaint' event, so for this I added the following in the onpaint: switch case(V.OUTPUT1>=(((V.TIME-shiftstart)/28800)*CELLTARGET1)) foreColor=RGB(0,255,0) case(V.OUTPUT1>=(((V.TIME-shiftstart)/32000)*CELLTARGET1)) forecolor=RGB(255,128,0) case(V.OUTPUT1<(((V.TIME-shiftstart)/28800)*CELLTARGET1)) forecolor=RGB(255,0,0) endcase Where CELLTARGET is amount expected in a shift on each cell (shift=28800 seconds), V.TIME is the number of seconds since midnight (Using Floor(SysTime()%86400)), shiftstart is when a new shift starts (in seconds since midnight) and V.OUTPUT is the count*number made per cycle. This is for cell1 and i pasted into then changed the variables for cells 2 to 12, but it works a treat, thanks for another great tip Quote Link to comment Share on other sites More sharing options...
AzeoTech Posted March 30, 2022 Share Posted March 30, 2022 Looks good. A few pointers: 1) whenever you think "copy" or "pasted" with code you should be thinking "can I use a function instead?". In your case, definitely. So, instead of copying all that code, create a sequence. Call it, say, "calcColor": function calcColor(output, celltarget) switch case(OUTPUT>=(((V.TIME-shiftstart)/28800)*CELLTARGET)) return(RGB(0,255,0)) case(OUTPUT>=(((V.TIME-shiftstart)/32000)*CELLTARGET)) return(RGB(255,128,0)) case(OUTPUT<(((V.TIME-shiftstart)/28800)*CELLTARGET)) return(RGB(255,0,0)) endcase Then, for example, in the event for cell1, you'd do: foreColor = calcColor(V.Output1, V.CellTarget1) It is slightly slower, processor wise, to do this, but it is SOOOO much cleaner programming wise. For one thing, if you decide, for example, that you want a slightly different shade of green, you only have to change it in one place. 2) Calculated V channels, although handy, are slower than simply having the calculation directly. Likewise, it hides the processor load of the calculation in the V channel. So, in your case, you reference V.Time, which a normal programmer would think is just a variable, so fast. But in fact, it is a calculation that is repeated up to 3 times in your case. As such, you'd be better off calculating V.Time in the code directly and storing in a private variable. Private variables are much faster than V channels. function calcColor(output, celltarget) private vtime = floor(systime()%86400) switch case(OUTPUT>=(((VTIME-shiftstart)/28800)*CELLTARGET)) return(RGB(0,255,0)) case(OUTPUT>=(((VTIME-shiftstart)/32000)*CELLTARGET)) return(RGB(255,128,0)) case(OUTPUT<(((VTIME-shiftstart)/28800)*CELLTARGET)) return(RGB(255,0,0)) endcase In fact, I'd probably just subtract shiftstart at the top and apply the multiplier too: function calcColor(output, celltarget) private precalc = (floor(systime()%86400) - shiftstart) * cellTarget switch case(OUTPUT>=precalc / 28800 return(RGB(0,255,0)) case(OUTPUT>=precalc / 32000 return(RGB(255,128,0)) case(OUTPUT< precalc / 28800 return(RGB(255,0,0)) endcase I'm not sure why you have some things /28800 and some /32000. 3) there is no reason to floor() the time. The math works as a floating point since you aren't doing ==. Quote Link to comment Share on other sites More sharing options...
BiggP Posted April 8, 2022 Author Share Posted April 8, 2022 Hi, Most of my choices for things come from the manual or the forum, I am inexperienced at coding, but learning slowly. I used the floor() so that I had a daily timer in seconds, for the shift changes and production target colouring. (V.TIME-shiftstart/28800)*CELLTARGET is how many parts should have been made to be on target, (V.TIME-shiftstart/32000)*CELLTARGET, is for 90% on target, so 4 hours into shift, with a celltarget of 200: V.TIME-shiftstart = 14400/28800 = 0.5*200 = 100, if output >= 100 production is on target and green is applied V.TIME-shiftstart = 14400/32000 = 0.45*200 = 90, if output >= production is 90% on target and orange is applied There is actually a mistake on my part the last case example should contain 32000 not 28800 so that if output is less than 90% it applies red Quote Link to comment Share on other sites More sharing options...
AzeoTech Posted April 9, 2022 Share Posted April 9, 2022 No worries. I wasn't suggesting you were wrong. I just wasn't sure what you were doing. But I will say: with modern computers, the cost of splitting calculations into constituent parts is quite negligible, but the readability gains are significant. As such, I recommend breaking apart 28800 and 32000 into their parts with recognizable numbers. I, for one, recognize 3600 as seconds in an hour, so: case(OUTPUT >= precalc / ((3600*8) / (100/100)) case(OUTPUT >= precalc / ((3600*8) / (90/100)) I'd probably use 0.9 instead of 90/100, but you get the idea. I assure you, in a year you will have totally forgotten why you put 32000 in there and won't recognize it for what it is, but the expressions I put will make it quite obvious. It also gets you to think through which number to use so you don't make the mistake you did where you put 28800 instead of 32000. Quote Link to comment Share on other sites More sharing options...
BiggP Posted May 23, 2022 Author Share Posted May 23, 2022 Just a quick update on some of the items in this thread. I had to make a massive change to the whole system, originally the count displayed was the count from the modbus device * parts (welded) per cycle, which I then used to set the cell history (the six numbers below the main count from post #4) and the colours according to if the number was on target or not. Much to my dismay, a power cut one day highlighted the fact that the I/O has no retention, so all my effort to make the I/O count the figure to display, were a waste of time (unless we added a UPS but where is the fun in that). So I created some test channels as 'internal counters' that goes up when the figure from the I/O goes up, no matter what it is, all was fine except when the software was closed and opened (like a powercut) the first cell history figure was always way out (like 100), because it was fetching history from the I/O count still. I remember when you said about using 'onpaint' to set colours and I also have a run once at startup script for variable declaration and clearing the operator names etc etc. It inspired me to streamline the whole cell history thing as it was using about 15 sequences and was a bit hit and miss on the colours plus it was grouped on each of the history numbers rather than the cell, so now in the startup script, I declare the global variable for each cell 60 min history as an array: for(private.x=1, x<13, x++) execute("global prodhistcell"+x+"={0,0,0,0,0,0,0}") endfor Then setting the v.channels (which is count*parts per cycle) current count as prodhistcell[0], I have a preserved baseline figure and i can use [1] to [6] instead of [0] to [5] when setting the history changes: for(private.x=1, x<13, x++) execute("prodhistcell"+x+"[0]=V.OUTPUT"+x) endfor Then set the ten minute production figure based on the shift target for each cell at startup: for(private.x=1, x<13, x++) execute("global tenminhistcell"+x+"=celltarget"+x+"/48") endfor Then it begins another looping sequence, which counts to 600 then shifts the 6th to 2nd in prodhistcell to 7th to 3rd, subtracts the figure saved at [0] from the current count to get the 2nd in array ([1]) and replaces the first number with the current count, th loops. for the colours, they are all set red in 'onload' event, then use a switch statement comparing the figure from prodhistcell array to 'tenminhistcell' figure to set it either red or green using 'onpaint' event. Also, because I have created a component for all six on one cell, it will be very easy to duplicate to the other cells. I know I probably overuse execute statements and am nowhere near scripting efficient, but to replace 15 sequences and ridiculous grouping with a few loops at startup, one script and a group component that actually works is great and it may be a mish mash to a degree, but I just wanted to say thanks for all the pointers which helped me achieve this. I also thought a couple of screengrabs might highlight other features introduced: At the end of the morning shift, you can see now there are operators added to display plus some extra icons in bottom left. The operators were a request from the section manager, which are set at software open or shift change, with the possibility of setting 1 or 2 staff. It has also been added to logging so db can be searched by name of staff. The display alternates between 2 pages every 20 seconds, so the padlocks numbered 1 and 2 simply lock display on either page, the last icon is to add staff. Because of having 3 shifts and a lot of agency staff, making it easy for shift leaders to add staff to a list for the correct shift was giving me a headache for a while. In the end, i used combo boxes to retain string data (names) which using virtual keyboard and adding to relevant shift just added to combo box. when shift changes, the set operator boxes cover the main number display so they will get done ASAP. If you click a name in list then exit you will close (hide) set operator box. if you click the add person icon at bottom of page the keyboard pops up, you type the name and select the shift to add it to. Then it appears straight away. Also if you click add instead of exit, you can add second operator, see azeotech appeared in list since first example. It just shows how flexible daqfactory is capable of being, it has done absolutely everything I have asked of it (even if diacritics were a PITA) Quote Link to comment Share on other sites More sharing options...
AzeoTech Posted May 23, 2022 Share Posted May 23, 2022 Looks real sharp! You might consider working your way towards using objects in your script. Pretty much any time you have multiple, identical things in real life that need representing in DAQFactory script, an object/class is the way to go. You basically create a template (a "class") that defines how the object works, along with member variables that characterize the object, then instantiate the class into objects, one for each real world item you need, usually storing that object in array with the rest of the similar objects. There are a few very useful things about objects in DAQFactory, over and above the normal usefulness in any programming language: 1) when you have an object stored in a variable called "myObject" and you do: curObject = myObject it does not copy the object, but instead copies the reference. I really used the wrong word in the previous sentence. Objects aren't "stored" in variables. Only the reference to the object is stored and that reference is what gets passed around. The advantage of this is that you can create a global variable like curObject and reference it to different objects depending on which thing you want to have the UI work with. This makes drill down super easy and scalable. 2) there is a function for member variables of objects (and really any variable) called "aliasFor" which makes the variable into an alias for another variable or, more usefully, a channel. That way you can use member variables in objects, which are very structured, to reference channels, which are quite flat (just a long list). The general format is: myVariable.aliasFor("myChannel") Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.