Use execSequence

Hi,

I would like to know how to use execSequence on FarmbotJS with NodeJS, and more particularly how to put parameters to a sequence. I would like to put a location or a tool/plant as a parameter (which the user choose before testing a sequence on the web application) in the
function. On the FarmbotJS documentation, it is explained that the execSequence function has 2 parameters : the first is the sequence ID, and the 2nd one is the “body”. I suppose that this is where I should modify the location parameter but I don’t know how to do that…
Could anyone help me ?

@jbonnard

That’s a great question. If you are using Typescript (or are willing to give it a try), FarmBotJS is fully typed, meaning that your editor would be able to provide you type hints for these sort of questions. Hovering over a function (or pressing f12) would show the function’s definition, including the schema of expected inputs.

Feedback Welcome

You are one of the first developers to ask about this and we are eager to provide you with an API you would like to work with. With that being said, there are still some parts of FarmBotJS that could be improved, since it has mostly been used by FarmBot employees with knowledge of system internals. The use case you mention may be one of those cases.

I could provide an alternative API in FarmBotJS if you have any ideas you would like to share. Ideally, I think it would be nice if developers did not need to write CeleryScript by hand, since it can be a challenge to learn.

After reading this response, I would like to ask you the question: “What would a simpler FBJS API look like to you?”. I look forward to hearing your feedback.

In the future, if you are ever unsure about the capabilities of FarmBotJS, it is important to note that the FarmBot Web App was written with FarmBotJS. Anything that can be done with the Web App can be done with FarmBotJS. I am here to answer your questions.

Before We Get Started

I wrote developer documentation to help new developers get started. The documentation is written as a guide (rather than a reference), so it is best to read the documentation from start to finish.

The most important part would be the section on CeleryScript, as it directly relates to the question you are asking.

I’m happy to clarify any of the parts that you have questions about.

Q: “How do I use FarmBotJS in Node”?

I have written an example Node application here. Please let me know if you have any questions.

Q: “How do I apply parameters to a sequence?”

To answer your question quickly, I’ve provided a solution below.

const Farmbot = require("farmbot").Farmbot;
const mySequenceId = 2323;
const myFarmbot = new Farmbot({ token: "***" });
const myToolNode = { kind: "tool", args: { tool_id: 4545 } };

myFarmbot
  .connect()
  .then(() => {
    myFarmbot.execSequence(mySequenceId, [
      {
        kind: "parameter_application",
        args: {
          label: "parent",
          data_value: myToolNode
        }
      }
    ]);
  });

Some Background Knowledge

Parameter passing is one of the more complicated parts of the application. Hopefully I am able to describe it properly. Here is some background information that will help you understand the system better.

Sequences offer a visual programming language. Programming languages are parsed into complex structures known as abstract syntax trees (ASTs).

When you edit a sequence in the Web App, you are editing the sequence AST directly. Behind the UI, there is a JSON tree structure that represents the sequence.

FarmBot sequence ASTs follow a strict JSON format known as “CeleryScript”. If you know how to read Typescript interfaces, you can see a description of all CeleryScript node types in this file.

You will need to write CeleryScript by hand to pass a parameter to a sequence. This is a moderately complex task and I am happy to work on simplified solutions. Feature requests welcome!

Line-by-Line Explanation

Assumptions:

  • You have already generated an access token.
  • The sequence you wish to execute has an id of 2323.
  • The tool you wish to pass to sequence 2323 has an ID of 4545.

Let’s go through this line-by-line:

First, we pull down the FarmBotJS library and create some local variables with relevant information. In a real world application, you would probably find this information via the REST API.

const Farmbot = require("farmbot").Farmbot;
const mySequenceId = 2323;
const myFarmbot = new Farmbot({ token: "***" });

Now things get more interesting. Since you mentioned that you want to pass a tool to your sequence, we will need to create a tool using the CeleryScript AST. A CeleryScript tool node is not the same thing as the tool resource you find in the REST API. You will use the tool_id from the REST API, though.

const myToolNode = { kind: "tool", args: { tool_id: 4545 } };

:point_up: It is important to note the variable name for later. We will add this CeleryScript tool to a CeleryScript parameter later on.

The next thing we need to do is connect to the server, then wait for success (via Javascript Promises)

myFarmbot
  .connect()
  .then(() => { /* Continued in next section... */ });

Once connected to the server, we need to call myFarmbot.execSequence(). This is more complicated than a traditional call to execSequence() because we are using parameters.

Here are some “secrets” about sequence parameters that might not be obvious if you have only used the sequence editor in the Web App:

  • Sequence parameters are passed to a sequence using the CeleryScript parameter_application node.
  • paramter_application nodes are placed into an array.
  • The array of parameter_applications is passed in as the second argument to execSequence.
  • Every sequence variable must have a label. Since the Web App only allows one variable per sequence, the default label for all sequences created in the sequence editor is parent. If you want to edit this variable from the Web App, I suggest you keep the name parent for your variable, otherwise you might not be able to see it from the UI.
  • It is technically possible to have a multi-parameter sequence, but do so with caution as we have not tested this usecase much.

With that being said, we need to create an array with a paramter_application pointing to our tool_id of 4545.

We use a parameter label of parent, since that is the label that all sequences expect (if they were created within the Web App):

    myFarmbot.execSequence(mySequenceId, [
      {
        kind: "parameter_application",
        args: {
          label: "parent",
          /** Remember: `myToolNode` was defined above */
          data_value: myToolNode
        }
      }
    ]);

Does this help? Please let me know.

3 Likes

Thank you for your help, it works ! :slight_smile:

I’m glad it works, @jbonnard. Please let me know if you need any other help. It’s always fun to see what third party developers are building.

By the way, I have another question. Is there a way to know if a sequence is finished or not ? Because when I wanted to run a succession of sequences with the execSequence function, it didn’t work properly… I think the problem is that if I execute two sequences consecutively, it executes the second one before finishing the previous one. Do you have any advice to solve this?

@jbonnard, yes there is a way to know when each sequence finishes, but you need to put a UUID in each execSequence request and look at each UUID inside the rpc_ok responses from the device. If that’s not clear I can provide a sample.

Using the JSON API ( which uses CeleryScript RPC calls to the device ) then yes, two or more sequence executions will be interleaved. I think @RickCarlino has this on his ( very long ) list of stuff to think about.

Meantime you can solve that issue and “serialize” sequence executions by watching for the UUID coming back in the response, as I wrote above.

1 Like

@jbonnard The advice from @jsimmonds is good, especially if you have a very complex app. With that being said, FarmBotJS already does this and as long as you are using JS Promises correctly, the sequences should execute in order.

I just wrote a code example for you and can confirm it works correctly on a real FarmBot. Are you calling execSequence from within a .then call?

To clarify, when you send an RPC to FarmBot with a label of ABC, FarmBot will reply with an rpc_ok message with a label that matches the original (ABC). If it receives a second RPC while the first execSequence is running, it will be buffered onto an internal message queue within FBOS and it will send you a second rpc_ok very quickly. rpc_ok does not mean that the operation has finished, however. It just means that the message has been queued. As a side note, this queue can be reset via an emergency lock / unlock.

If you send one message before waiting for an rpc_ok from FBOS, race conditions may occur. If you are using FBJS correctly, FBJS will handle queuing for you via Promises.

Please let me know if this helps- I am happy to answer any questions you may have.

@RickCarlino Wow ! I was unaware of farmbot-js and it’s capabilities . . impressive :slight_smile:

Being a Node.js kind-a guy, I’ll be using this a heap more !

In CoffeeScript I just tested that RPC serialization of Sequences does work as advertised

bot
  .connect()
  .then ->
    bot.execSequence 30677
  .then ->
    bot.execSequence 28377

As this is the Software Development sub category, I have a little “by-the-way” :slight_smile:

This program doesn’t exit out of Node.js . . presumably there’s an async. event outstanding (?)
Any ideas ? Thanks !

global.atob = require 'atob'
util        = require 'util'
{ Farmbot } = require 'farmbot'

bot = new Farmbot {token: "J0eXAeyiOi (redacted :)"}

bot
  .connect()
  .then ->
    bot.execSequence 30677
  .then ->
    bot.execSequence 28377
  .finally ->
    util.log 'done -'

@jsimmonds

I was unaware of farmbot-js and it’s capabilities

Glad you like it! The other great thing about FBJS is that it is always up-to-date since the Web App is essentially a GUI frontend to FBJS. Anything that the Frontend can do is possible with FBJS.

This program doesn’t exit out of Node.js

I’m not sure this is a bug. Since MQTT.js never stops listening for events on the socket (and is still connected), it would make sense that the event loop never clears.

Some ideas for one off scripts that need to exit:

  • Call process.exit(0) when you are done.
  • Try disconnecting from MQTT. This might be possible via current_bot.client.end() but I haven’t tried, since all of the FBJS use cases we’ve hit involve keeping the client open indefinitely.

If you find a way to add a cleaner bot.disconnect method, I’d be happy to accept it as a PR.

@RickCarlino . . I’m the bug this time ! Had a brain fade, sorry :slight_smile: You’d be 100% correct about MQTT and probably other packages with things in the “Event loop” as well.

1 Like

@RickCarlino, tested that and it works just fine ! Thanks. Again, you’d be 100% correct : only MQTT.js keeps the Event loop alive.

1 Like

@jbonnard @RickCarlino FBOS is unable to distinguish more than 1 RPC client i.e. an FBJS client can still cause interleaved Sequence execution while a FarmEvent Sequence is concurrently running.
Maybe this is something that can be improved ? I’ll see what solution I can come up with.

[edit] Errr…, maybe scratch the above.
FarmEvents are not RPC clients; they’re pre-compiled blobs, ready to go.
More thought required.

[edit+] Tested 2 farmbot-js-based clients concurrently and Sequences can become interleaved (!) i.e. steps of the each concurrent sequence are intermingled.

@jsimmonds This is true. The way to fix it would be to perform
“transactions”. Unfortunately this requires writing CeleryScript by hand.

Another solution would be to write a wrapper sequence that contains two EXECUTE
blocks back-to-back, but this will not work for dynamic usecases where the
sequence is not known at compile time.

What We Have Today

Here’s how multiple RPCs can be wrapped together into an RPC “transaction”:

farmbotObject.send({
      kind: "rpc_request",
      args: {
        // NOTE: FarmBotJS exports a `uuid()` helper if you
        //       prefer using real UUIDs (safer).
        label: "123456",
        priority: 0
      },
      body: [
        { kind: "execute", args: { sequence_id: SEQUENCE1 } },
        { kind: "execute", args: { sequence_id: SEQUENCE2 } },
      ]
    })

I don’t like this API, but the use case is seldom hit, so I focused on higher priority features.

Ideally, third party developers shouldn’t need to write CeleryScript by hand
unless they are doing very advanced operations. Obviously, executing two
sequences in a row does not constitute an “advanced operation”, so it would be
ideal to provide a better API for users.

I will outline an idea I have considered (but not implemented).

IDEA: Add a “Transaction” Helper

The idea would be to add transaction or batch helper to FBJS that has the exact
same API as traditional FBJS, but wrapped in a function to encapsulate a single atomic
“transaction” or RPC batch.

Here is the example above, converted to a “transactional style”:

farmbotObject.transaction((transaction) => {
  transaction.execSequence(SEQUENCE1);
  transaction.execSequence(SEQUENCE2);
})

Nice, and maybe medium effort.

I agree that this use case only arises when FB Users are not mindful of what scheduled FarmEvents they’ve set up and what their own “remote-control” RPC client software is doing :slight_smile:

I feel slightly guilty of distracting your attention with this discussion right now.
By the way, is the dets-based telemetry any use in sleuthing the current OTA update problem reports ?

I feel slightly guilty of distracting your attention with this discussion right now.

No worries! I quite enjoy helping with third-party dev issues- It’s nice to see what folks build!

is the dets-based telemetry any use in sleuthing the current OTA update problem reports

Maybe. I will reach out to Connor today and see if he has any ideas (I did not author the telemetry parts, so my knowledge is still limited).

I tried to send a parameter to Sequence 28377 with this code

#
#
#

global.atob = require 'atob'
#axios       = require 'axios'
util        = require 'util'
{ Farmbot } = require 'farmbot'

#

seqArg = {
  kind: "parameter_application",
  args: {
    label: "parent",
    data_value: {
      kind: "identifier",
      args: {
        label: "fred"
      }
    }
  }
}

#

bot = new Farmbot {token: "t"}

bot.on 'logs', (data, eventName) ->
  util.log "#{util.inspect data.message}"

bot
  .connect()
  .then ->
    bot.execSequence 30677
  .then ->
    bot.execSequence 28377, [ seqArg ]
  .catch (e) ->
    util.log util.inspect e
  .finally ->
    util.log 'done -'
#    bot.client.end()

and got an error response from the RPC

29 Apr 18:18:11 - Error: Problem sending RPC command: function :erl_eval.unsafe_ZnJlZA/0 is undefined or private

Is an “identifier” invalid for a Sequence parameter ?

Another question :slight_smile: : How can I retrieve a Sequence parameter declaration by using the “canned” UI Sequence Builder buttons . . or fetch the parameter “by name” from a Sequence step ? :confused:

Parameterized Sequence construction seems very bespoke and “behind-the-scenes” a.t.m.
Am I wrong :thinking: ?

@jsimmonds

It looks like you are diving into the most complicated part of the application.

Ideally, third-party developers should never need to touch these internals, but that’s still a work in progress.

As you learn these internals, I would be interested to hear what sort of API would be better suited to your needs.

In the meantime, I will do my best to explain this :sweat_smile:

Is an “identifier” invalid for a Sequence parameter ?

Putting an identifier in that particular location is akin to calling a variable that does not exist (has not been declared) yet.

Pseudo code example:


my_function(foo) # Foo is not yet declared.

foo = 123

Although it is syntactically correct to put an identifier into a data_value, it is not semantically correct (and, yes, we need to find ways to make this much more obvious or less error prone).

There are times when it would be OK to put an identifier there (such as an execute block inside of a wrapper sequence) but in this case, it is calling an undefined identifier.

How can I retrieve a Sequence parameter declaration by using the “canned” UI Sequence Builder buttons . . or fetch the parameter “by name” from a Sequence step ? :confused:

I might not be understanding your question, but are you asking how to get the name of a paramter in a sequence? If that’s the case, the good news is that the Web App only uses one label: for parameters currently- parent. If you made the sequence within the sequence editor, the only parameter available will be parent. Please let me know if I am misunderstanding your question.

Parameterized Sequence construction seems very bespoke and “behind-the-scenes” a.t.m.

We really need to find better ways to expose these APIs to developers. I am fairly certain this forum thread is the first time a third party developer has tried to access these functions. FarmBot has a unique position because we expose a graphical programming language (and its AST specification) to third party devs. I can’t think of too many applications in this exact situation.

One idea I have had is to just add a lua block (similar to the assert block) to the RPCs and allow devs to send strings of Lua code. We could then expose parameters as Lua variables (eg: Type parent instead of dealing with identifier, parameter_declaration, variable_declaration etc…). It’s just an idea, though. I welcome your feedback.

Thanks so much @RickCarlino.

I must be upfront and say that I’m really just tinkering around and lifting the hood occasionally :wink:
I don’t have one simple Use Case, though, if pressed, I’d say it’s probably along the the lines of “Remote Control”.

lua blocks sound good. JS blocks even better, but callable Node.js on an embedded platform might be a big ask :slightly_smiling_face: ( akin, perhaps, to using a large CNC saw just to cut firewood ! )

That “question” was seriously malformed, sorry. Please ignore it.

Time for me to learn some Anatomy and Physiology of Sequences . . by reading more source code :bookmark_tabs:

1 Like

The CeleryScript entry in the documentation is a good starting point, but you may be one of the first people to comb through it. I am happy to answer any questions you have (and update the docs accordingly).

JS blocks even better, but callable Node.js on an embedded platform might be a big ask

There are some alternatives out there for embedded systems which I have had my eyes on, such as Duktape. As you have pointed out, Node.JS is probably a bit too much for our use case.

1 Like