Nested sequences and Plant Groups

As a software developer, I like to keep things DRY (Don’t Repeat Yourself). As such, I’ve set out to create a large amount of atomic sequences (that I’ve called Actions) that can be composed together in parent sequences (that I’ve called Routines), and using the directory feature (thanks for this!) to somewhat keep my sanity.

I’ve just tested a seeding and watering sequence that runs on a Plant Group consisting of three plants, and I’ve bumped into an issue where it does not behave as desired (although while building out the sequences it dawned on me that it could behave the way I just observed).

Sequences

Action: Collect seed from seed bin

This sequence consists of:

  • Execute sequence: “Home safely”.
  • Safe Z Move to seed bin with an offset appropriate for the mounted needle.
  • Turn vacuum peripheral ON.

Action: Deposit seed at 5 mm below soil height

  • Safe Z Move to externally defined location, with an offset taking into account soil height, length of the needle and the desired depth of the deposit.
  • Turn vacuum peripheral OFF.
  • Mark As Status: Planted the externally defined location variable.

Subroutine: Sow 1x at 5 mm from seed bin

The 1x refers to the fact I desire one trip to the seed bin. Some seeds, like watermelon, are so big the needle will only catch one of them at a time, yet it’s a good idea to sow 4-5 seeds and thin later. Watermelon would use a Sow 5x at 12 mm from seed bin sequence.

This subroutine consist of:

  • Execute sequence: “Collect seed from seed bin”.
  • Execute sequence: “Deposit seed at 5 mm below soil height”. This sequence requires a variable definition, which is set to “externally defined”, passing it up further the chain.

Routine: Sow 1x at 5 mm for each plant from seed bin

  • Execute sequence: “Get Seeder”. This mounts the Seeder toolhead.
  • Execute sequence: “Sow 1x at 5 mm from seed bin”. This sequence requires a variable definition, which is set to “externally defined”, passing it up further the chain.
  • Execute sequence: “Return Seeder”. This unmounts the Seeder toolhead.

Plant Groups

Radishes A

A plant group with 0 automatic filters and 3 manually selected radish plants.

Regimens

Radish

The Radish test regimen has a Day 1 event set to “Sow 1x at 5 mm for each plant from seed bin”. This sequence requires a variable definition, which is set to “externally defined”, passing it up further the chain.

Farm Events

Radish Regimen

The Farm Events list contains an event that triggers the “Radish” regimen for today. This regimen requires a variable definition, which is set to “Radishes A”, passing the list of 3 radishes down the chain all the way to the “Sow 1x at 5 mm for each plant from seed bin” sequence.

Expected behaviour

  • Execute sequence: “Get Seeder”.
  • Execute sequence: “Sow 1x at 5 mm from seed bin” for Radishes A plant #1.
  • Execute sequence: “Sow 1x at 5 mm from seed bin” for Radishes A plant #2.
  • Execute sequence: “Sow 1x at 5 mm from seed bin” for Radishes A plant #3.
  • Execute sequence: “Return Seeder”.

Observed behaviour

  • Execute sequence: “Get Seeder”.
  • Execute sequence: “Sow 1x at 5 mm from seed bin” for Radishes A plant #1.
  • Execute sequence: “Return Seeder”.
  • Execute sequence: “Get Seeder”.
  • Execute sequence: “Sow 1x at 5 mm from seed bin” for Radishes A plant #2.
  • Execute sequence: “Return Seeder”.
  • Execute sequence: “Get Seeder”.
  • Execute sequence: “Sow 1x at 5 mm from seed bin” for Radishes A plant #3.
  • Execute sequence: “Return Seeder”.

How can I improve my nested sequences, regimens and farm events to achieve the expected behaviour without giving up the modularity of my sequences?

1 Like

I think one way to make it work is to pass in the “Radishes A” group earlier. For example, in the “Sow 1x at 5 mm for each plant radish” sequence. That stops the variable from being passed on to the parent, which normally makes the entire sequence loop-able.

But then I will need to make plant-specific sequences and that just doesn’t make sense to me. In my opinion, sequences should be almost completely plant-agnostic. They are simply a collection of commands to do things that every plant requires. Regimens (again in my opinion) are where things become plant-specific. A sequence is “water lightly” and “water heavily”. Regimens pick and choose the correct sequence at the correct Day to make it care for a type of plant specifically.

But even regimens are agnostic to which exact plants in your bed it targets. Farm Events take care of that. I don’t want to create a “Radishes A” regimen and an identical “Radishes B” regimen that targets another group. Both groups require the exact same plant care that a regimen provides. Both groups just need the care of a single, group-agnostic Radish regimen. The Farm Event ties the regimen to the group.

But as far as I can see (I really hope I’m overlooking something), the software won’t allow me to set up the dependencies like that. It seems to make things work correctly, I need to bake the groups into the sequences and simply call them from the Farm Events.

I don’t want to do that. Is there a way around this?

OK @mdingena we have done some investigation on our end. All four of us discussed your issue at our Friday planning meeting and although we think we understand the problem, no one was 100% certain, as the setup criteria seems a bit complex / hard to follow.

We think (and we might be wrong on this) that there is a misunderstanding about how variables get passed.

When you pass a point to child, the Celeryscript runtime will always pass the point to the child.

Pseudocode:

var point = point(123);
mySequence(point);

When you pass a point group to a child, the Celeryscript runtime will always transform the call to an iteration one level deep and no deeper than that.

So if you start with this:

var myGroup = [point(123), point(456)];
mySequence(myGroup);

The celeryscript runtime will, behind the scenes, tranform it to this:

mySequence(point(123));
mySequence(point(456));

The transformation stops there. It will not perform a deep transformation. Point groups cannot be passed around more than once since they are transformed one time. It is best to think of point groups similar to a spreadsheet range rather than an array in a traditional programming language.

EDIT I: If you’ve worked with functional languages that support macros, you can think of pointGroup passing more like a macro and less like a map iterations. PointGroup passing happens at “compile time” rather than run time.

2 Likes

Side note: The inability to pass point groups around is for extremely technical reasons and we do want to offer this ability eventually. It was a compromise we made so that the point groups feature could get in the hands of customers quickly rather than us needing to delay the release until a 100% perfect solution is available.

1 Like

If point groups can’t be “passed through”, then it’s impossible to pass a group to a sequence that does:

  • One-off command
  • Looped command
  • One-off command

In other words, it’s impossible to water many plants with just a single “mount watering nozzle” and “unmount water nozzle” action bookending both sides of the loop.

I don’t understand how everyone else uses their FarmBot. Does everyone else just hardcode the plant groups into their sequence? Then you can never reuse a sequence… you’ll have a “water my radishes” sequence and a “water my strawberries” sequence and a “water my basil” sequence. You’re just repeating yourself over and over and if you want to change the watering amount, now you’ll have to edit all those sequences rather than just one watering sequence that you can reuse.

Honestly, I can’t wait for this feature to exist.

@mdingena

You add a wrapper sequence or apply the groups at a lower level. There’s light repetition, but not like the pre-FBOS 8 days.

Here’s a typical workflow:

  • You create groups that match certain criteria. Eg: crop type, plant stage, etc…
  • You add plants to that group by ensuring they match the criteria (eg: don’t manually add them).
  • You create a single sequence that does one thing. Example: “Water a plant”, “photograph a plant”.
  • You create a wrapper sequence that applies the “one thing” to many plants. Eg: “Photograph all plants”, “Water all radishes”.
  • You schedule the wrapper sequence in a farm event.

In the case above, you would ideally have only one sequence. You instead create two: an isolated action (water, photograph, etc…) plus a “wrapper” that applies that isolated action to a group. It’s inconvenient for advanced use cases, but it’s by no means a show stopper- in this case the ideal scenario would be a single sequence, but we instead needed to create two.

As you change a plant’s properties, it will move in and out of different groups. As its membership in the group changes, the sequences that will act upon each individual plant will change also (the group membership is calculated immediately before the sequence runs).

This workflow is incompatible with Genesis devices because we need tool mounting and unmounting sequences that the Express doesn’t need. We have no place to put the mounting sequences as a one-off action. You must either leave them out (so you water with no nozzle) or you add them, but then the nozzle is mounted and unmounted for every plant. Both of these are no good to me.

The only way currently is to schedule a mounting sequence in Farm Events, then later schedule another Farm Event that does the watering, and last you schedule an unmounting sequence an estimated time after the watering. I really hope that is not the end state of sequences because this is not a good way of dealing with the problem.

Watering sequences should be runnable on their own. In my opinion, that includes preparing the UTM for the task. I do not want to decouple tool mounting and actions, they are inherently related. In my opinion we must be able to colocate these.

I am a Genesis owner also. I have a “lab” setup for an Express bot, but my real bot in the Garden is a Genesis model and I am able to perform all operations on it just fine with wrapper sequences.

I would not recommend trying to time your farm events in this manner. I don’t think it will work (though I haven’t tried), and you won’t find that recommendation anywhere in the docs. The best way to get around the limitation is to EXECUTE the mount/unmount sequence in the wrapper sequence before / after iteration runs:

Example:

“WATER ALL” Wrapper Sequence:

  1. Mount watering nozzle
  2. Execute the “WATER A PLANT” sequence and pass in a group XYZ.
  3. Unmount watering nozzle.

“PHOTOGRAPH ALL” Wrapper Sequence:

  1. Execute the “PHOTOGRAPH PLANT” sequence and pass in group XYZ.

“WATER AND PHOTOGRAPH ALL” Wrapper Sequence:

  1. Execute “WATER ALL”
  2. Execute “PHOTOGRAPH ALL”

It is not. Like a lot of things on the roadmap, it takes time and resources.

1 Like

This is the issue. You cannot make a generic “water plants lightly” sequence because you must hardcode the group into the sequence.

You cannot have:

  • Water plants lightly
  • Water plants heavily

You must instead make

  • Water radishes lightly
  • Water cherry tomatoes lightly
  • Water strawberries lightly
  • Water basil lightly
  • Water bok choy lightly
  • Water chives lightly
  • Water watermelon heavily
  • Water cucumber heavily
  • Water zucchini heavily

As soon as you make it a generic sequence by passing in an external variable instead of a hardcoded plant group, a Genesis will start mounting and unmounting for every plant.

Your software documentation (v13) hints at the fact we should be able to do this.

This sequence can only be used to water the specific Spinach plant chosen. Instead of making watering sequences for every plant in your garden, make this sequence a generic Water plant sequence that can be used on any plant by using a location variable.

Source. Bold emphasis mine.

But this won’t work. Well, it does, but Genesis will mount and unmount the toolhead for every plant.

1 Like