OnPing Introduces Inferno!
Inferno is a new programming language created by Plow Technologies. Any time a company decides to make a language it should be viewed with skepticism. Learning a (new) programming language always has a higher barrier to entry than using an existing language. So why did we do it?
First, let’s review our current language: structured-script. We chose this language because we knew automation professionals already used IEC 61131-3 languages for PLCs programming. We hoped the transition from PLC programming to OnPing scripting would be smooth and easy.
It wasn’t.
Below is an example illustrating structured-script syntax:
onPingCommLossTimeIndex := 1;
onPingCommLossTime := latestInput(onPingCommLossTimeIndex);
if (isUnit(onPingCommLossTime)) then
onPingCommLossTime := 1440.0;
End_If;
numberOfCommunicatingDevices := 1;
deviceOneIndex :=2;
deviceOneAcc := 0;
WITH t FROM now - minutes(round(onPingCommLossTime)) TO now EVERY minutes(1) DO
a := input(deviceOneIndex,t);
IF not(isUnit(a))
THEN deviceOneAcc := deviceOneAcc + 1;
END_IF;
END_LOOP ;
output := 0;
IF deviceOneAcc == 0 then
output := 1;
End_If;
So if you don’t know structured text you might not know which things are non-standard. We highlighted an example from the code, and you can see it here:
WITH t FROM now - minutes(round(onPingCommLossTime)) TO now EVERY minutes(1) DO
Without prior familiarity, this non-standard statement could easily trip a lot of people up. This is the type of confusion we want to get rid of while writing scripts for OnPing. Another common example was “isUnit(a)
“. Those two items represent 2 of the 3 most important concepts in the entire virtual and control parameter ecosystem:
- Time Manipulation – You can refer to moments in time and move back and forth.
- Missing Data – In our scripting system, missing data is a serious issue.
We want these concepts to feel magical in OnPing, but the old scripting tools frequently led PLC programmers to trip over non-standard syntax. Meanwhile, people with data science or IT backgrounds were just not into the syntax of the rest of it. To people used to languages like python or javascript the syntax felt super clunky. We knew we wanted a change there but were left with a decision to make about what to use.
Affordance is a concept that is super helpful when thinking about design. In the case of programming languages it is about what the language makes easy versus what it makes hard. We want a language that is designed to encourage correct use. In the case of general purpose programming languages (python, c# etc) a lot of choices are balanced to give users good experiences over a wide range of uses. OnPing offers the luxury of considerable ability to constrain use space.
What we want to make easy:
- to write code translating input into output.
- to refererence time and historical values.
- to use special properties of data in OnPing.
- to write code that tends to be correct.
- for machines to analyze code ‘extra’ properties.
What we want to make hard:
- to cause the system to pause.
- to slow the system down.
- to create accidental surprises.
Introducing, Inferno
Now, let’s look at some examples of inferno! First a script to detect when a parameter stops changing value.
Detecting whether a parameter continues to change value can be done many ways. One common way is to compare a max value to a min value of a range to see if they match. Which leads to our first snippet of code:
let maximum = Array.reduceRight
(fun b a ->
match a with {
| None -> (Some b)
| Some x -> (if (x < b) then (Some b) else (Some x)) })
None
Val
This is a pretty radical departure from structured-script! Let’s walk through different parts.
We are taking advantage of the replacement for isUnit() in structured-script. A concept that is used heavily in functional programming.
(Some b) or None
This allows you to use a value b if it is present. But if no value is present you get a None.
The keywords let in Some None match .. with
are all borrowed from the OCaml programming language. At Plow, a lot of our backend is heavily functional and we think it is a great fit for the sort of data translation that often takes place in OnPing. One of the things we wanted to make easy was translating inputs to outputs. This is literally what functions do!
Let’s look at another code snippet…
in let values = [valueAt target t | t <- Time.intervalEvery
(Time.minutes 1)
start
?now]
This snippet shows off a few more new ideas in inferno that are really important. We already had great facilities in structured-script for manipulating time. Now those time functions are grouped together in a module called ‘Time’.
Instead of setting up a loop, inferno allows us to use an array to process the data. You might also notice that we are referring to ‘valueAt’ instead of ‘input’. Also, we are able to name our inputs with descriptive names like target . This helps keep everything much easier to track. Again we want to make it easy to write good scripts.
I always like to have full code examples to go along with the snippets. Here is the full script that we wrote…
let minutesToStale = match latestValue interval with {
| Some x -> round (x * 60.0)
| None -> 0 }
in let delta = match latestValue delta with {
| Some x -> x
| None -> 1.0e-2
}
in let start = ((?now) - (Time.minutes (round minutesToStale)))
in let values = [valueAt target t | t <- Time.intervalEvery
(Time.minutes 1)
start
?now]
in let val = Array.keepSomes values
in let maximum = Array.reduceRight
(fun b a ->
match a with {
| None -> (Some b)
| Some x -> (if (x < b)
then (Some b)
else (Some x)) })
None
val
in let minimum = Array.reduceRight
(fun b a ->
match a with {
| None -> (Some b)
| Some x -> (if (x > b)
then (Some b)
else (Some x)) })
maximum
val
in match (maximum, minimum) with {
| (Some mx, Some mn) -> if (abs (mx - mn)) <= delta
then 1.0
else 0.0
| _ -> 0.0 }
Another change you may have noticed here is there is no reference to output. Instead when you get to the bottom of the script you are done. This is another artifact of inferno being functional. It really forces everything to be thought of as translation of inputs to outputs.
Resolution
OnPing’s TachDB historian is a unique sort of high speed Time Series Database including multi-resolution rollups. OnPing can traverse a large set of data quickly at low resolution looking for a key spot to dive in and make changes. In the past, support for adjusting the resolution took place outside of the scripting language. By supporting the ability to make resolution adjustments in the scripting language itself, we are able to do some pretty neat things. Here is an example.
let isSparse = fun res ->
let ?resolution = toResolution res
in let test = Array.length (Array.keepSomes [valueAt t | t <- Time.intervalEvery (Time.seconds res)
(?now - (hours 3))
?now ])
in if test > 0 then #false else #true
in isSparse 64
Now we get a test to see if at a given resolution a parameter is sparse. To be fair I am defining sparse here as
“Missing any values at all” and that isn’t a normal definition but it is indicative that you can do data quality tests concerning availability of data at a certain resolution.
Built-In Revisions
Personally, one of the most obnoxious thing about programming in an embedded context like a website or tool is the lack of revision. Version control is baked in deeply to the inferno system.
Automatic script rollouts and updates are built in and deployed on change. But not till it is ensured that the new version of the script is compatible with the existing parameters. If it is found that it is not, the user is given the option to create a copy of the script as a jumping off point.
We have more plans for this for sure! Being able to examine the internal structure of the programs being written gives us a lot of power to do automated version updates.
The Editor
Our new editor has features like:
- Auto-complete
- Function lookup
- Revision load
- Type checking
- Type hinting
It is a huge step forward from where we were and again there is more to come. We want to bring our time travel features into the editor and that effort is well under way!
Migration
We are currently in the middle of migrating our many virtual parameters. We of course are going to be going slowly and carefully. Double checking things as they go but so far so good! In the meantime we are now going to be working on virtual parameters exclusively in inferno. As a product, we may temporaritly have an awkward time where control parameters are still going to be written one way and virtual another. Howver, we believe this to be short lived.
Three Directions From Here
Machine Learning and Stats
Another area we are wanting to build on with inferno is statistical inference and modeling. There are loads of times that people are pulling our data out of OnPing and into other tools to build regressions. We want to make it easier to load those insights back into OnPing and make them part of your real-time systems. A few items we specifically want:
- Simulated parameters
- Ad-hoc anomaly detection
- Scriptable data classification
By building these capabilities deep into our systems we think we can make it easier to use machine learning in industrial systems. A task which has just been so difficult historically.
To Control Parameters
Control parameters are already so powerful! By adding inferno we will be able to better define good path and bad path. We also intend to do a complete rewrite of the control parameters engine! More on that some other time.
Parameter Creation in Many Screens
Making Parameters in OnPing is more arduous than we would like. We want you to be able to make virtual parameters in tables and alarms, not just in the assignment screen. This is coming too!
Feedback
If you are interested in trying out the new Inferno Engine we are looking for users to give us their experience feedback in these early days. Please reach out to support@plowtech.net to ask about becoming an early adopter of inferno.