Actions Tutorial#

NotationScript#

Before you can understand how ActionLang works, you need to understand what NotationScript is. NotationScript, in its simplest form, is a format to describe data, similar to JSON. Using NotationScript to program ActionLang is preferred over JSON because NotationScript is designed to be shorthand for JSON.

{
  "type": "print",
  "message": "Hello there"
}

In NotationScript you could instead have something like this:

print "Hello there"

In the above example, print "Hello there" is a node. print is the name of the node and "Hello there" is an argument of that node. In the case of the print node, ActionLang is configured to use the node’s first argument as the argument to "message".

In other words, you can effectively “compile” NotationScript to JSON. In fact, that’s what it’s designed for at its core. NotationScript is just shorthand for writing JSON.

Note

The above is an example that is specific to ActionLang, but NotationScript itself is just a format. The behavior for a node print "Hello there" is not strictly defined to result in the JSON shown above.

ActionLang#

ActionLang is usually described in NotationScript format, but can also be described using raw JSON (deprecated). Before explaining how to write ActionLang programs, we need to explain the concept of actions.

An action does something when it is run. Some actions may finish immediately, other actions may take time until they are “done”. For instance, the print and log actions are done immediately after running, but a wait action does not finish immediately.

ActionLang has many built-in actions to help describe the order actions are executed in or if they are executed in parallel. Maybe one action ending will cause the start of another. Here are some examples that are not specific to SolarThing that show how ActionLang can be used.

A simple program#

This program aims to show how simple ActionLang can be.

// queue is a type of action that takes a list of actions and executes those actions in sequence.
queue {
  print "Hello there"
  // These are the same
  print("Hello there")

  // When passing an argument to a node without parenthesis, if you do not quote that argument it will be interpreted as a string.
  print Hello

  // parallel is a type of action that takes a list of actions and executes those actions in parallel
  parallel {
    queue {
      // The wait action takes an ISO-8601 duration as its argument.
      //   The action is effectively a timer, and becomes done once the given duration is up
      wait PT5S
      print "5 seconds are up!"
    }
    queue {
      wait PT10S
      print "10 seconds are up!"
    }
  }
}

Notice that in the outer most block, there is only a single action: queue. Inside of the queue action are other actions. As you see above, depending on the action, you can nest actions inside of actions to get the behavior you want.

You can run this simple program using solarthing action file_name.ns. (Or docker: cat config_templates/actions-ns/simple_program.ns | docker run -i --rm ghcr.io/wildmountainfarms/solarthing action -) The result is this:

Hello there
Hello there
Hello
5 seconds are up!
10 seconds are up!

Normally you won’t ever use the solarthing action command, but it can be a useful tool for understanding ActionLang. If you run the program on your own machine, you would see that the line x seconds are up! are run after 5 and 10 seconds respectively. The simplicity of ActionLang allows for simple and complicated sequences of instructions over time.

The race action#

The race action is one of the most powerful actions in ActionLang. It can be used like an if statement, or as a statement to only do one thing depending on what action is done first.

race {
  racer(wait PT5S) : print "5 seconds won!"
  racer(wait PT10S) : print "10 seconds won!"
}

In the above example, you have two actions competing to “win” the race (wait PT5S and wait PT10S). The wait PT5S action will finish first so its corresponding action (print "5 seconds won!") will be executed. There are many creative uses for the race action that you might not think of initially. Take this example:

race {
  racer(perform-some-action-that-takes-time) : pass
  racer(wait PT30S) : print "Timed out!"
}

In the above example, perform-some-action-that-takes-time takes some time to complete, and there is a chance that performing that action may never finish. If the action finishes within 30 seconds, the pass action will be run, which is a placeholder for doing nothing and being done immediately. If the action does not finish within 30 seconds, the action will be forcefully ended and the print "Timed out!" action will be run.

You can also use the race action as an if statement.

race {
  racer(is-complete) : do-something
  racer(pass) : do-something-else
}

In the above example, we assume that is-complete is either done or is not done. If is-complete is done (true), then do-something is executed. If is-complete is not done, then pass is checked to see if it is done. Since pass is always done immediately, do-something-else would be run in this case.