Instant messaging with FarmBot

Correct. It will attempt to auto reconnect, also. If you are concerned about the token expiring still you could:

  1. Store the token in an object and use setInterval to refresh the token when needed.
  2. Check bot.client.connected to see if the bot is still connected.
  3. Put a .catch() after your .then() calls to handle failure.

The specific implementation would vary based on the project, but that’s more or less how you could do it. Are you experiencing any of these issues right now?

I may add a bot.connected helper soon to make this more obvious. Like I mentioned earlier, you are the first or second person building a project with FarmBotJS (that we know of), so feedback is always appreciated.

@RickCarlino Not experiencing issues per sé, just trying to wrap my head around things. These questions just pop up while I’m thinking about how I have to design and handle things.

It’d be nice if there was a way to write the program as if the token and connection are already handled without having to explicitly handle those.

Still learning Promises. So maybe I’m just approaching it wrong. I just feel like I’ll be “backpaddling” a lot when in each action my program does I first need to check the state of FarmBotJS before I ask it to do stuff.

I’ll play with it some more, write a working prototype. And we’ll take it from there.

@RickCarlino Could you please tell me if the Farmbot object throws an error if it can’t be created with new, and if throwing will implicitly reject() my own Promise?

_ensureConnection( token ) {
	return new Promise( function( resolve, reject ) {
		if( !this._farmbot ) {
			// @RickCarlino this is the line I'm asking about 
			this._farmbot = new FarmBot({ token: token.encoded, secure: true });
		}
		if( !this._farmbot.client.connected ) {
			this.says( "I'm gathering my senses, hang on... \u{1F50E}" );
			this._farmbot.connect().then( function() {
				resolve();
			}, function() {
				reject();
			});
		} else {
			resolve();
		}
	}
}

If I read correctly, throwing error will implicitly reject the promise.

Rejections happen when a promise is explicitly rejected, but also implicitly if an error is thrown in the constructor callback

@mdingena Correct, If you are in the “executor” of a promise (the function that is passed in to new Promise) and an exception is throw, it will trigger the catch() branch.

Here’s an example that will always trigger catch().

1 Like

Success! :slight_smile: One step closer

_requestToken() {
	console.log( "Requesting token" );
	return axios.post( this.config.farmbot.url, this.config.farmbot.secret );
}

_ensureToken() {
	let _this = this;
	return new Promise( function( resolve, reject ) {
		console.log( "Promising token..." );
		if( !_this.config.farmbot.token ) {
			console.log( "No existing token found" );
			_this._requestToken().then( function( response ) {
				console.log( "New token received" );
				_this.config.farmbot.token = response.data.token;
				resolve( response );
			}, function( response ) {
				console.log( "No new token received" );
				reject( new Error( request.statusText ) );
			});
		} else {
			console.log( "Existing token found" );
			if( _this.config.farmbot.token.unencoded.exp <= Math.floor( Date.now() / 1000 ) ) {
				console.log( "Token has expired" );
				_this._requestToken().then( function( response ) {
					console.log( "New token received" );
					resolve( response );
				}, function( response ) {
					console.log( "No new token received" );
					reject( new Error( request.statusText ) );
				});
			} else {
				console.log( "Token still valid" );
				let response = {
					data : {
						token : _this.config.farmbot.token
					}
				};
				resolve( response );
			}
		}
	});
}

This property doesn’t seem to exist, though. Stumped once more :cry:

Glad to hear things are working out!

With regards to bot.client.connected I have a few questions:

  • Is it bot.client that does not exist, or bot.client.connected?
  • Are you using Typescript? If so, you might not see this as an autocomplete suggestion, because I don’t have it listed on the interface (yet).

FarmBot.client is undefined…

Celebrating some small victories :smile:

4 Likes

@RickCarlino I think I’ve cracked a working example (but still missing your bot.connected helper).

Created a priming method that ensures FarmBot is still responding before attempting to do anything. Right now I’m not sending any real commands to FarmBot yet (since my physical bot isn’t on right now). In my FarmGram constructor I listen for the “ping” chat message:

this._telegram.onText( /ping/i, () => this.ping() );

When FarmGram received “ping” in chat, it will execute the ping() method:

_prime() {
	let _this = this;
	return new Promise( function( resolve, reject ) {
		_this._ensureToken()
			.then( () => _this._ensureConnection() )
			.then( () => resolve() )
			.catch( () => {
				_this.say( "Couldn't prime your command! \u{1F614}" );
				reject();
			})
		;
	});
}

ping() {
	this.say( "Received ping command." );
	this._prime()
		.then( () => this.say( "Pong" ) )
		.catch( () => this.say( "Durr..." ) )
	;
}

The idea is that each method will use the _prime() Promise. Now it’s a matter of powering up my FarmBot and listening for some more words in chat, and see if I can get it to move around a bit :slight_smile:

Next step would be to listen for some events coming from FarmBotJS, and hooking up some chat messages to that. I’ll need your help with those events emitted by FarmBotJS.

@RickCarlino First time I’ve published an npm package. I’d appreciate pointers regarding the packaging and the readme.

Here are the repo and the npm package.

@RickCarlino

  • bot.client.connected is working. My problem was that I checked for those properties before I used bot.connect(). I’m handling those cases now, doing .connect() only when not already connected.
  • When the token expires, how should I proceed? Should I create a new Farmbot object with the new token or is there a way to feed the new token to the existing instance?
  • I’ve been able to move my physical FarmBot using Telegram!

I think the most useful feature of this idea is that FarmBot can inform you via IM rather than controlling it via IM.

So it’d be great if you have some detailed documentation regarding Farmbot events.

I’m mostly interested in events about:

  • Starting or stopping a sequence or regimen.
  • Motor stalls.
  • Disconnects.

I think I just need more verbosity in the logging. Perhaps you could make some logging levels that increase in verbosity, which others can use in their programming as required?

[details=Click to see my wall of text] (1) Starting sequence “Water The Carrots”.
(1) Executing sequence “Attach Watering Tool”. <-- called by parent sequence “Water The Carrots”
(1) Starting sequence “Attach Watering Tool”. <-- stepping into child sequence “Attach Watering Tool”
(2) Moving to 2532,1230,0.
(3) Homing Z-Axis.
(3) Relative move to 0,0,-230.
(3) Homed Z-Axis.
(2) Moving to 60,225,0.
(3) Absolute move to 60,225,0.
(2) Moving to 60,225,493.
(3) Relative move to 0,0,493.
(2) Moving to 85,225,493.
(3) Relative move to 25,0,0.
(2) Moving to 85,225,0.
(3) Homing Z-Axis.
(3) Relative move to 0,0,-493.
(3) Homed Z-Axis.
(1) Finished sequence “Attach Watering Tool”. <-- last event from child sequence
(1) Executed sequence “Attach Watering Tool”. <-- we’re back in parent sequence “Water The Carrots”
(1) Executing sequence “Water Carrot Row One”. <-- called by parent sequence “Water The Carrots”
(1) Starting sequence “Water Carrot Row One”. <-- stepping into child sequence “Water Carrot Row One”
(2) Moving to 200,200,0.
(3) Absolute move to 200,200,0.
(1) Executing sequence “Medium Water”. <-- called by parent sequence “Water Carrot Row One”
(1) Starting sequence “Medium Water”.
(2) Activating Peripheral “Solenoid Valve”.
(3) Writing Pin 9. Value is now 1.
(2) Waiting.
(3) Wait 2300 milliseconds.
(2) Deactivating Peripheral “Solenoid Valve”.
(3) Writing Pin 9. Value is now 0.
(1) Finished sequence “Medium Water”.
(1) Executed sequence “Medium Water”. <-- we’re back in parent sequence “Water Carrot Row One”
(2) Moving to 200,300,0.
(3) Absolute move to 200,300,0.
(1) Executing sequence “Medium Water”. <-- called by parent sequence “Water Carrot Row One”
(1) Starting sequence “Medium Water”.
(2) Activating Peripheral “Solenoid Valve”.
(3) Writing Pin 9. Value is now 1.
(2) Waiting.
(3) Wait 2300 milliseconds.
(2) Deactivating Peripheral “Solenoid Valve”.
(3) Writing Pin 9. Value is now 0.
(1) Finished sequence “Medium Water”.
(1) Executed sequence “Medium Water”. <-- we’re back in parent sequence “Water Carrot Row One”
(2) Moving to 200,400,0.
(3) Absolute move to 200,400,0.
(1) Executing sequence “Medium Water”. <-- called by parent sequence “Water Carrot Row One”
(1) Starting sequence “Medium Water”.
(2) Activating Peripheral “Solenoid Valve”.
(3) Writing Pin 9. Value is now 1.
(2) Waiting.
(3) Wait 2300 milliseconds.
(2) Deactivating Peripheral “Solenoid Valve”.
(3) Writing Pin 9. Value is now 0.
(1) Finished sequence “Medium Water”.
(1) Executed sequence “Medium Water”. <-- we’re back in parent sequence “Water Carrot Row One”
(1) Finished sequence “Water Carrot Row One”.
(1) Executed sequence “Water Carrot Row One”. <-- we’re back in parent sequence “Water The Carrots”
(1) Executing sequence “Water Carrot Row Two”.
[… same as above …]
(1) Executed sequence “Water Carrot Row Two”.
(1) Executing sequence “Detach Watering Tool”.
[… kind of like the “Attach Watering Tool” sequence …]
(1) Executed sequence “Detach Watering Tool”.
(1) Finished sequence “Water The Carrots”.[/details]

I’ve posted this on GitHub for FarmBotJS.

Also, I noticed that toggling pins always give two identical status events. (Pin set to 1 (twice) and pin set to 0 (twice)).

@mdingena

That’s great!!! :tada:

I’m going to add these to our internal TODO list. I really appreciate that you are bringing this up- most of FarmbotJS use has been for browser users, who typically connect once and disconnect after using the app. Having a long running Node JS server is a new use case that we will need to make some tweaks for.

With regards to token expiration, re-instantiating a new FarmBot instance is probably the fastest way to go.

If you need more verbosity (especially if you are just reading statuses and not sending commands) you can also listen to the MQTT channels (using MQTT.js directly). They are very verbose.

These are the MQTT channels that FarmBot uses:

bot/device_1234567/from_clients inbound commands (from users)
bot/device_1234567/from_device command responses
bot/device_1234567/status status updates
bot/device_1234567/logs Logs (what you see in the status bar)

That’s great news! Does FarmBotJS easily expose these?

@RickCarlino from_device channel gives me hashes labels. What am I supposed to do with those?

If I move my bot 1 mm on X axis, I get messages on from_device and status. But neither can be used to say, for example, “moving to [destination]”.

Do I need to convert that label?

{"kind":"rpc_ok","comment":null,"body":[],"args":{"label":"514cfb3c-0cc7-45ba-9f26-6845c6ae361d"}}

@mdingena:

Labels

the label attribute is used to help the sender and receiver figure out which message is being talked about (message order is not guaranteed in MQTT). An rpc_ok message is saying that “The command with label “514c…” succeeded”.

I started writing documentation for the structure of these messages this morning for you and any other users who would like to know, but it is not yet finished.

You might be able to collect the labels locally to infer what just happened, but it might not be easy.

Status

The /status channel is more useful. It represents everything that the bot knows about itself at any point in time. If the bot’s status changes is any way, it will re-send the entire “state tree” over this channel.

This one is probably useful for what you are doing.

Logs

This might be useful right now, until we can work out some sort of event system. Perhaps you could use String.prototype.includes to infer the meaning of a log and forward alerts over telegram?

Hope that helps. You are one of our first 3rd party developers, so a lot of the documentation will be based off of the questions you ask. I appreciate the feed back. Please check back in a few days as I update the Celery Script documentation. I think this will clear up a lot of the questions you have.

@RickCarlino

The /status channel gives what the bots knows about itself, indeed, but it doesn’t know what it’s going to do, for what purpose. /status isn’t enough to build IM interaction around. Because as the receiver of Instant Messages, you don’t really care about the bots current state, you are only interested in it starting and finishing stuff on its own.

I have the feeling nowhere in your logging you can really read something like

“I’m starting sequence X.”
“I’ve finished sequence X.”
“I think I’ve stalled at X,Y,Z… sadface
“I’ve just finished watering ‘Carrot Row One’.”
“Here’s a picture of your carrots! [attachment]

With /status or /from_device, I can mostly make something like

“11:41 Now I’m at X,Y,Z”
“11:41 Now I’m at X,Y,Z”
“11:42 Now I’m at X,Y,Z”
or
“I set Pin 10 to 1.”
“I set Pin 10 to 0.”

The bot doesn’t really know what it’s doing at a higher level. You need to verbosely log this. So I feel your code needs more log entries at specific points, so that 3rd-party devs can really hook into what’s going on with the bot at a higher level.

This will take some time for you to implement I’m sure. This is just me telling you what I’d need, ideally. Perhaps I can join in on the development of this, if you just point me in the right direction.


I’ve noticed that /logs forcefully includes the bot’s name at the beginning of the log message. For example, I’ve named my FarmBot “Botje”. Now, when using “Send Message” action in Sequence builder, I can assign the message to success, warning or error, which are the type of logs my Telegram bot is listening for.

The message above don’t have “Botje”, but the log messages do:

1 Like

All very valid points, @mdingena. It will def. take us a while to get it to that level. If you are feeling motivated and want to try to implement such code ahead of us, your best place to start would be FarmBot OS, as that is where all the logging happens. FBOS is written in Elixir and uses the Nerves Project.

As a side note from our discussion earlier, I just remember that @connor actually wrote a token refresh mechanism into FarmBot OS. Any tips for token refreshing on long running clients, Connor?

@conner any update regarding this?

@mdingena

Sorry didn’t see this ping because my name was spelled wrong.
The bot gets a new token every 2 hours i believe.

as for “IMing the bot” logs probably won’t be useful for that… We have no real format for log messages and i don’t know if we plan to support such a thing from the Logger view. Logs will probably remain for the consumption of the user.

now ive been toying around in my head a more standardized system for these kind of messages. They would be similar to the log system, but for consumption of an outside source (via farmware, mqtt, email, etc) I don’t currently have an implementation example, or a spec laid out or anything. I’d be interested to know what you are thinking.
If you like you can Private message me and I can give you a way to contact me and we can discuss this as a proper feature request.

@RickCarlino and I plan on using a new mqtt broker at some point in the relatively near future that will make systems like this a little bit easier to construct and maintain.

1 Like