Simple API to read and write to Ignition tags

I have 3 working Python, quasi driver programs I would like to incorporate into Ignition applications. Unfortunately some of the modules required by the existing programs are not available in Ignition’s Jython 2.1 and users do not have the ability to add modules. The 3 program are as follows:

  1. Application that extracts data from a remote web page (requires urllib2, re and BeautifulSoup)
  2. Application that calculates local sunrise and sunset times (requires math and pytz)
  3. Application that communicates with serial port devices (requires serial)

Ignition has other ways to work around this problem but each have their disadvantages.

  1. Convert the programs into OPC-UA servers. This would require the purchase of a third party OPC-UA driver toolkit and a somewhat steep learning curve for using the toolkit. Not a good solution when only needing to transfer a few tag values and don’t want the complexity of OPC which is optimized for transferring large numbers of tags.
  2. Rewrite the programs into an Ignition modules using the new module toolkit. I would rather not have to re-write the applications especially since I do not have experience in Java.
  3. Use Ignition’s python scripting with the functionality of unavailable python modules re-written in Java. Again, requires some re-writing and knowledge of Java. Also, I doubt keeping a serial port open in a python script would work very well since Ignition scripts are not designed to run continuously.

What would be nice to have is a simple API that allows any external program written in any language that support sockets to read and write Ignition tags. A simple protocol maybe using JSON would be great. The API could also be used with external MES and ERP applications.

I realize not using the security features available to OPC-UA has some issues but the API socket could be off by default. Anyone wanting to use the feature would have to explicitly turn it on. Also, tens of thousands of current OPC-DA server are currently operating with the same risk.

You could use DB tags.

There is a paper somewhere here that says how to setup a DB tag table that can be driven externally.
Basically you would define the tags in a database then your Python script would update the value and lastchange columns when something changes.

Thanks, that is an option but it requires installing and maintaining a SQL database and excludes Panel Edition.

I think there are more options than what you list. I’ll think about this some more this weekend, but was just about to run out the door, saw this, and wanted to reply (these points are mostly just general observations, not direct replies to your points):

  • Our module SDK essentially can be OPC-UA toolkit - you can write drivers that then get exposed through our OPC-UA server.

  • Someone could write a module that basic acts as a python interface to our API. That is, build a module that has a newer version of jython and lets you load other python libraries.

  • Your simple JSON interface could probably be written very easily using the current SDK.

I’m most interested in the 2nd point. I’ve always thought we should do something like this… but of course, ideas are cheap around here, and it just hasn’t been very high on the list.

Anyhow, as I said, I just wanted to throw some things out there- everyone can think about it and come back on tuesday :smiley:

VBA Code to calculate Sunrise and Sunset times in Auckland, NZ. Will need a bit of a tweak to Parameterize and Pythonorize it but it is all proven in the field.

[code]Sub Main
Dim vSunRiseTime As String
Dim vSunSetTime As String
Dim Season As String
Dim vTime As Date

vTime = Now
If GetTimeZone = "New Zealand Daylight Time" Then
    Season = "Summer"
Else
    Season = "Winter"
End If
vSunRiseTime = SunRiseSet("SunRise", 4) '4 minutes past sun rise
vSunSetTime = SunRiseSet("SunSet", 30) '30 minutes past sunset

End Sub

Private Function SunRiseSet(vSunRiseOrSunSet As String, vMinuteOffset As Integer) As String
Dim vTimeOffset As Double
Dim vLatitude As Double
Dim vLongitude As Double
Dim vZenith As Double
Dim vTimeZone As Integer 'Number of hours from UTC
Dim pi As Double
Dim vYear As Integer
Dim vDays As Integer
Dim lngHour As Double 'Longitudinal hour
Dim SinDec, CosDec As Double
Dim T As Double 'Local mean time of rising / setting
Dim Mdeg As Double
Dim Ldeg As Double
Dim RA As Double
Dim LQuadrant, RAQuadrant As Integer
Dim COSh As Double
Dim H As Double
Dim UT As Double
Dim LocalT As Double
Dim vHour As Integer
Dim vMinute As Integer

pi = 4 * Atn(1)
vLongitude = 174.766667  'Degrees: Auckland
vLatitude = -36.866667  'Degrees: Auckland
vZenith = 90.833 'Sun's zenith for sunrise/sunset
If Season = "Summer" Then
    vTimeZone = 13
Else
    vTimeZone = 12
End If

'Find the number of days so far this year
vYear = DatePart("yyyy", vTime)
vDays = DateDiff("d", DateSerial(vYear, 1, 1), vTime) + 1

'Convert longitude to hour value and calculate the approximate time
lngHour = vLongitude / (360 / 24)

'Approximate rising time in decimal days
If vSunRiseOrSunSet = "SunRise" Then T = vDays + ((6 - lngHour) / 24)

'Approximate setting time in decimal days
If vSunRiseOrSunSet = "SunSet" Then T = vDays + ((18 - lngHour) / 24)

'Calculate the Sun's mean anomaly
Mdeg = (0.9856 * T) - 3.289 'Degrees
Ldeg = Limit360(Mdeg + (1.916 * Sin(Radians(Mdeg))) + (0.02 * Sin(2 * Radians(Mdeg))) + 282.634) 'Degrees
RA = Limit360((180 / pi) * Atn(0.91764 * Tan(Radians(Ldeg))))
LQuadrant = (Floor(Ldeg / 90)) * 90
RAQuadrant = (Floor(RA / 90)) * 90
RA = RA + (LQuadrant - RAQuadrant)
RA = RA / 15
SinDec = 0.39782 * Sin(Radians(Ldeg))
CosDec = Cos(ArcSin(SinDec))
COSh = (Cos(Radians(vZenith)) - (SinDec * Sin(Radians(vLatitude)))) / (CosDec * Cos(Radians(vLatitude)))

’ if if rising time is desired:
If vSunRiseOrSunSet = “SunRise” Then H = 360 - ((180 / pi) * ArcCos(COSh))
’ if setting time is desired:
If vSunRiseOrSunSet = “SunSet” Then H = (180 / pi) * ArcCos(COSh)

H = H / 15
T = H + RA - (0.06571 * T) - 6.622
UT = Limit24(T - lngHour)
LocalT = Limit24(UT + vTimeZone) 'Decimal hours
vHour = Floor(LocalT)
vMinute = (LocalT - vHour) * 60
'Apply offset
vMinute = vMinute + vMinuteOffset
If vMinute >= 60 Then
    vMinute = vMinute - 60
    vHour = vHour + 1
End If
SunRiseSet = Format(vHour, "00") & ":" & Format(vMinute, "00")

End Function

Private Function Limit24(vX As Variant) As Double
Do
If vX > 24 Then vX = vX - 24
Loop Until vX < 24
Do
If vX < 0 Then vX = vX + 24
Loop Until vX >= 0
Limit24 = vX
End Function

Private Function Limit360(vX As Variant) As Double
Do
If vX > 360 Then vX = vX - 360
Loop Until vX < 360
Do
If vX < 0 Then vX = vX + 360
Loop Until vX >= 0
Limit360 = vX
End Function

Private Function Radians(vX As Variant) As Double
Dim pi As Double
pi = 4 * Atn(1)
Radians = vX * pi / 180
End Function

Private Function ArcCos(vX As Variant) As Variant
'Radians in, radians out
'Arccos(X) = Atn(-X / Sqr(-X * X + 1)) + 2 * Atn(1)
ArcCos = Atn((-1 * vX) / Sqr(-1 * vX * vX + 1)) + (2 * Atn(1))
End Function

Private Function ArcSin(vX As Variant) As Variant
'Arcsin(X) = Atn(X / Sqr(-X * X + 1))
ArcSin = Atn(vX / Sqr(-1 * vX * vX + 1))
End Function

Public Function Ceiling(ByVal X As Double, Optional ByVal Factor As Double = 1) As Double
’ X is the value you want to round
’ is the multiple to which you want to round
Ceiling = (Int(X / Factor) - (X / Factor - Int(X / Factor) > 0)) * Factor
End Function

Public Function Floor(ByVal X As Double, Optional ByVal Factor As Double = 1) As Double
’ X is the value you want to round
’ is the multiple to which you want to round
Floor = Int(X / Factor) * Factor
End Function

Private Type SYSTEMTIME
wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer
End Type

Private Type TIME_ZONE_INFORMATION
Bias As Long
StandardName(0 To 31) As Integer
StandardDate As SYSTEMTIME
StandardBias As Long
DaylightName(0 To 31) As Integer
DaylightDate As SYSTEMTIME
DaylightBias As Long
End Type

Private Declare Function GetTimeZoneInformation Lib “kernel32.dll” (lpTimeZoneInformation As TIME_ZONE_INFORMATION) As Long
Public Function GetTimeZone(Optional ByRef strTZName As String) As String
Dim objTimeZone As TIME_ZONE_INFORMATION
Dim lngResult As Long
Dim i As Long
lngResult = GetTimeZoneInformation&(objTimeZone)
Select Case lngResult
Case 0&, 1& 'use standard time
GetTimeZone = -(objTimeZone.Bias + objTimeZone.StandardBias) 'into minutes
For i = 0 To 31
If objTimeZone.StandardName(i) = 0 Then Exit For
strTZName = strTZName & Chr(objTimeZone.StandardName(i))
Next
Case 2& 'use daylight savings time
GetTimeZone = -(objTimeZone.Bias + objTimeZone.DaylightBias) 'into minutes
For i = 0 To 31
If objTimeZone.DaylightName(i) = 0 Then Exit For
strTZName = strTZName & Chr(objTimeZone.DaylightName(i))
Next
End Select
GetTimeZone = strTZName
End Function[/code]

Creating a SQLTag provider using the API really is not that hard. Give it a shot and I think you will be able to figure it out.