Tuesday, February 17, 2015

Ignition Template Canvas



Template Canvas, like the Template Repeater, will repeat templates. However, you are not limited to one template, size, position, or parameters. For this example, I created a new window and added a Template Canvas.

This time, I want to use two different templates with my data type AI_STD: Alarm Analog and Scaling Analog templates. Also, I wanted to get rid of the scroll bars by displaying only enough data that would fit on one page. As the display size can change from computer to computer, the number of templates added to each page will vary. To access other pages, I created a third template, Page Button, to add page buttons dynamically to each page.

This is a screenshot of the result:





























I added the following custom properties: blanks, page, path, totalpages. Blanks adds spaces between the tags. Page is the current page to display. Path is the parent path to look for my tag data type: AI_STD. Totalpage is the total number of pages needed.







I created a new script called “template_analog_TC”. This script will return the desired templates with the correct parameters for each tag, if they are on the desired page, and add page buttons horizontally across the bottom as needed. I call this script anytime the 'page' property changes using the propertyChange event handler on the Template Canvas.


def template_analog_TC(event):
import system

#Let's get the custom properties from the event.
path = event.source.getPropertyValue('path')
blanks = event.source.getPropertyValue('blanks')
canvas_height = event.source.height
page = event.source.getPropertyValue('page')

# Create the rows for the dataset
rows = []
tags = system.tag.browseTags(parentPath = path, udtParentType = 'AI_STD', sort ="ASC")

row_count = 0
page_count = 1
x = 0
y = 0
height = 35
width = 1050
layup = 'n/a'
template = 'Items/Alarm Analog V2'

template2 = 'Items/Scaling Analog V2'
width2 = 975

template3 = 'Items/Page Button'
height3 = 50
width3 = 125

maxheight = int(canvas_height) - int(height3) # Don't want to cover the buttons to change pages.

for tag in tags:
#print "On line 267, This tag ", tag.name, " has row count: ", row_count
readtags = [tag.path + "/USESCALE", tag.path + "/USEHHALM", tag.path + "/USEHALM", tag.path + "/USELALM", tag.path + "/USELLALM"]
scl,hh,h,l,ll = system.tag.readAll(readtags)
temprows = []

if scl.value:
name = tag.name + "SCALE"
y = height * row_count
parameters = '{"TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template2, layup, x, y, width2, height, parameters]
temprows.append(row)
row_count += 1
name = tag.name + "SCL"
y = height * row_count
parameters = '{"Alarm":"ALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if hh.value:
name = tag.name + "HHALM"
y = height * row_count
parameters = '{"Alarm":"HHALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if h.value:
name = tag.name + "HALM"
y = height * row_count
parameters = '{"Alarm":"HALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if l.value:
name = tag.name + "LALM"
y = height * row_count
parameters = '{"Alarm":"LALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if ll.value:
name = tag.name + "LLALM"
y = height * row_count
parameters = '{"Alarm":"LLALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if (scl.value or hh.value or h.value or l.value or ll.value) and blanks: #Add a blank space for separation between tags.
row_count += 1
if ((not scl.value) and (not hh.value) and (not h.value) and (not l.value) and (not ll.value)): # There is nothing to add.
print "This tag ,", tag.name, " is not used..."

if (y < maxheight): # Yes, these will fit on the page...
if (page == page_count): # Add these rows
rows = rows + temprows
else:
page_count += 1
row_count = 0 # clear the row count...
temprows = [] # Clear the temprows...

if scl.value:
name = tag.name + "SCALE"
y = height * row_count
parameters = '{"TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template2, layup, x, y, width2, height, parameters]
temprows.append(row)
row_count += 1
name = tag.name + "SCL"
y = height * row_count
parameters = '{"Alarm":"ALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if hh.value:
name = tag.name + "HHALM"
y = height * row_count
parameters = '{"Alarm":"HHALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if h.value:
name = tag.name + "HALM"
y = height * row_count
parameters = '{"Alarm":"HALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if l.value:
name = tag.name + "LALM"
y = height * row_count
parameters = '{"Alarm":"LALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if ll.value:
name = tag.name + "LLALM"
y = height * row_count
parameters = '{"Alarm":"LLALM", "TagPath":' +'"'+ tag.path +'"'+'}'
row = [name, template, layup, x, y, width, height, parameters]
temprows.append(row)
row_count += 1
if (scl.value or hh.value or h.value or l.value or ll.value) and blanks: #Add a blank space for separation between tags.
row_count += 1

if (page == page_count): # Add these rows if this is the correct page
rows = rows + temprows


# Add the button at the bottom.
for button_count in range(page_count):
temprows = []
name = "Button " + str(button_count)
if button_count == (page - 1):
pass # This is the page we are on...
else:
if button_count ==  0:
x3 = 0
else:
x3 = (width3 * button_count) + (10 * button_count)
y = canvas_height - height3
parameters = '{"page": "' +str(button_count+1) + '", "text":' +'"Page '+ str(button_count+1) +'"'+'}'
row = [name, template3, layup, x3, y, width3, height3, parameters]
temprows.append(row)

rows = rows + temprows

# Create the dataset to send back.
headers = ["name", "template", "layup", "x", "y", "width", "height", "parameters"]
data = system.dataset.toDataSet(headers, rows)

# Let's send the data back.
event.source.templates = data
event.source.setPropertyValue('totalpage', page_count)



The tricky part is getting the buttons to set the 'page' property on the Template Canvas.
The easiest way I found was go to the Top Level and drill back down.

Here is the code for the actionPerformed on the event handler of the button.


from com.inductiveautomation.factorypmi.application.components import TemplateCanvas
# Get the custom property 'page' from the button.
page = event.source.parent.page

#Get the component count and the components below the 'Root Container'
count = event.source.getRootPane().getLayeredPane().getComponents()[0].getComponentCount()
components = event.source.getRootPane().getLayeredPane().getComponents()[0].getComponents()
for i in range(count):
#Look for the TemplateCanvas instance, then check if the page property exists.
if isinstance(components[i],TemplateCanvas):
if 'page' in components[i].dynamicProps:
components[i].setPropertyValue('page', page)




So I can now make all my analog pages dynamically and they fit on to any size monitor.


Monday, February 16, 2015

Ignition Template Repeaters

New to Ignition V7.7 are Template Repeaters and Template Canvas. These two tools can save tons of time, if you use them to their full potential. When you combine Tag Data Types and templates, you can now dynamically build pages with ease.


The Template Repeater allows you to repeat one template as many times as you want. The Repeat Behavior allows two choices: Count and Dataset.







Count is the simplest. Just select Count in the Repeat Behavior, then put the number you want in the Repeat Count. On the template itself, you need to add the template property: 'index' as an integer. This will get the instance number of the template. Then use this index number in an sql query to get a tag path from a database.


Dataset is the the other Repeat Behavior. Each name column in the dataset is the parameter you would like to pass. For my example, I created a template for Analog Instrument Alarms. I need two parameters to make the template work, Alarm and Tagpath. So I created those columns.












For each row of data I add to the dataset, another template is added to the repeater. If the templates outgrown the template repeater size, a scroll bar will be added to the side and/or bottom.











Now you can add each row of data manually; however, I am using Tag Data Types. And this template is for use with my AI_STD data type. I added a custom property to the Template Repeater called 'path'. I then add the parent path that I want to look for my Data Type: 'AI_STD' in. Now I call my script 'template_analog' to build my data table. I bind the Template Parameters to my script and pass the path I want it to look under. Then have it return the dataset. This makes the page dynamically every time the page is loaded. I also look to see if the scaling, high high, high, low, or low low alarms are used and if so add the row to the dataset.

Here is the code for the 'template_analog' script:


def template_analog(path):
import system

# Create the rows for the dataset
rows = []
tags = system.tag.browseTags(parentPath = path, udtParentType = 'AI_STD', sort ="ASC")
row_count = 0

for tag in tags:
readtags = [tag.path + "/USESCALE", tag.path + "/USEHHALM", tag.path + "/USEHALM", tag.path + "/USELALM", tag.path + "/USELLALM"]
scl,hh,h,l,ll = system.tag.readAll(readtags)
row_count += 1

if scl.value:
sclrow = ["ALM", tag.path]
rows.append(sclrow)
if hh.value:
hhrow = ["HHALM", tag.path]
rows.append(hhrow)
if h.value:
hrow = ["HALM", tag.path]
rows.append(hrow)
if l.value:
lrow = ["LALM", tag.path]
rows.append(lrow)
if ll.value:
llrow = ["LLALM", tag.path]
rows.append(llrow)

# Create the dataset to send back.
headers = ["Alarm", "TagPath"]
return system.dataset.toDataSet(headers, rows)




With this simple code, I can create all the analog alarms used instantly. It saves time and also avoids mistakes. Hope this helps with your next project.


Friday, February 13, 2015

Ignition's Calendar Components


I have created a few python scripts for use with the Calendar Components in Ignition. First, I would like to start with how to default to today's date when the window (or template) with the Calendar is opened. 




In the property editor under 'Data', bind the code   now(0)   to the Date field. This will push today's date into the calendar only when the window is opened as the poll rate is 0. 

Next, I added a the following custom properties to the calendar.

  • day - The day of the week we wanted.
  • DOW - Day of Week we want.
  • previous - This allows the previous week's day to be returned.

With these variables, I run my script 'getday' to get the correct day I want. This code should be called anytime the date changes.


Here is that code.


def getday(date,dow,previous = 0):
     from java.util import Calendar

c = Calendar.getInstance()
c.setTime(date)
dayofweek = c.get(Calendar.DAY_OF_WEEK)

#Determine what day to return...
if dow == 'Monday':
newday = dayofweek - 2
elif dow == 'Tuesday':
newday = dayofweek - 3
elif dow == 'Wednesday':
newday = dayofweek - 4
elif dow == 'Thursday':
newday = dayofweek - 5
elif dow == 'Friday':
newday = dayofweek - 6
elif dow == 'Saturday':
newday = dayofweek - 7
else: #Default day is Sunday....
newday = dayofweek - 1

if (previous == 1) and (newday < 0):
newday += 7

c.add(Calendar.DATE, -newday)
day = c.getTime()


return day



The other common calendar date wanted is the last day of the month. To do this is simple.




def lastday(date):
from java.util import Calendar

c = Calendar.getInstance()
c.setTime(date)

lastday = c.getActualMaximum(Calendar.DAY_OF_MONTH);

return lastday



Here is a window that has two calendar's on it. It has the example calendar and another calendar to get the same result. You need to have Ignition v7.7.2 or newer.

https://drive.google.com/file/d/0B-p6QXt0ucZwLTl6dzJ3UnZ0LWM/view?usp=sharing







Welcome to my blog!



Hello, my name is Chris Powell. This is my blog for offering tips to use Ignition SCADA by Inductive Automation. I started using Ignition in late 2011 (Version 7.2). Inductive Automation and it's strong base of users keep improving it all the time. These tips are my way of helping.

Hope you enjoy!