farmbotsimulator-js
Version:
A simulator for the farmbot agricultural robot in nodejs
1,395 lines (1,338 loc) • 50 kB
JavaScript
/* Copyright 2020 Brian Onang'o
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
/**
* @module stateController
*/
import mqtt from 'mqtt'
import axios from 'axios'
import to from 'await-to-js'
import events from "events"
const FARMBOTURL = "https://my.farmbot.io/api"; // FamBot REST API
const paths = {
"device": "device",
"points": "points",
"savedGardens": "saved_gardens",
"regimens": "regimens",
"sequences": "sequences",
}
/**
* stateController
*/
class stateController extends events {
// /*
// * simulates uptime. Starts updating on successful log in
// */
// uptime = 0;
// /*
// * updates uptime
// */
// timer;
// /*
// * mqtt broker as contained in unencoded FarmBot token
// */
// broker;
// /*
// * FarmBot authorization token
// */
// token;
// /*
// * Id of FarmBot device as contained in unencoded FarmBot token
// */
// botId;
// /*
// * If device is online
// */
// deviceOnline = false;
// /*
// * If other clients connected
// */
// otherClients = false;
// /*
// * mqtt connection status
// */
// mqttStatus = false;
// /*
// * FarmBotUserEnv
// */
// userEnv = {};
// /*
// * end effector location
// */
// location = {
// x: 0,
// y: 0,
// z: 0
// };
/**
* Constructor
* @param {Object} options
* @param {function} options.events - event emitter
* @param {boolean} options.debug - display debug messages
* @param {number} options.port - port to communicate with Mathematica simulation application
*/
constructor(/*options = {}*/) {
super();
// this.events = options.events;
// this.debug = options.debug || false;
// this.port = options.port || 8787
/*
* simulates uptime. Starts updating on successful log in
*/
this.uptime = 0;
/*
* updates uptime
*/
this.timer = null;
/*
* mqtt broker as contained in unencoded FarmBot token
*/
this.broker = null;
/*
* FarmBot authorization token
*/
this.token = null;
this.botId = null;
/*
* If device is online
*/
this.deviceOnline = false;
/*
* If other clients connected
*/
this.otherClients = false;
/*
* mqtt connection status
*/
this.mqttStatus = false;
/*
* FarmBotUserEnv
*/
this.userEnv = {};
/*
* end effector location
*/
this.location = {
x: 0,
y: 0,
z: 0
};
this.status = {
mqtt: 'waiting'
}
this.refreshTokenData();
}
/**
* getToken
*/
getToken(){
return this.token
}
/**
* Start simulator
*
* @returns {Promise} A promise that is resolved if token is valid but rejected otherwise
*/
async startOrRestartSimulator() {
this.refreshTokenData();
this.stopTimerRunTimer();
this.emit('status', { message: 'mqtt waiting for connection' });
this.status.mqtt = 'waiting';
// let waitingForConnection = setInterval(() => {
// this.emit('mqtt', 'waiting for connection');
// }, 1000)
let [err, care] = await to(this.connectMQTT())
if (err) {
throw err
}
// clearInterval(waitingForConnection);
// mqttStatus = true;
this.client = care;
// this.monitorMQTT();
// this.emit('mqtt', 'connected')
this.emit('status', { type: "positive", message: 'mqtt connected' });
this.status.mqtt = 'connected';
this.scheduleMonitorDeviceStatus();
this.schedulePublishTelemetry();
this.schedulePublishLogs();
this.schedulePublishStatusMessage();
this.monitorDownlinkMessages();
// [err, care] = await to(this.monitorDeviceStatus());
// let isOffline = err ? true : false
// console.log(err)
// console.log(care)
// console.log('passed online. Is offline:', isOffline)
// console.log("connected");
// let client = this.client;
// client.subscribe(`bot/${this.botId}/#`, {
// qos: 1
// });
// console.log('going to try ping')
// await tryPing();
// console.log('tried ping')
// client.on("message", (wholeTopic, message) => {
// this.processMessage(wholeTopic, message)
// })
}
/**
* Create headers
*/
headers() {
let headers = {
'Authorization': `Bearer ${this.token}`,
'content-type': "application/json"
}
return {
headers
}
}
/**
* get saved gardens
* @returns {Promise} - Resolves with list of gardens
*/
async savedGardens() {
return new Promise(async (resolve, reject) => {
let [err, care] = await to(axios
.get(`${FARMBOTURL}/saved_gardens`, this.headers()));
if (err) {
return reject(
err
);
}
resolve(care.data)
});
}
async points() {
return new Promise(async (resolve, reject) => {
let [err, care] = await to(axios
.get(`${FARMBOTURL}/points`, this.headers()));
if (err) {
return reject(
err
);
}
resolve(care.data)
});
}
async farmbotGET(item) {
return new Promise(async (resolve, reject) => {
let pathItem = paths[item];
if (!pathItem) {
pathItem = item.replace(/([a-z])([A-Z])/, (match, a, b, offset, string) => a + '_' + b.toLowerCase())
}
// if(!pathItem)pathItem = item
let [err, care] = await to(axios
.get(`${FARMBOTURL}/${pathItem}`, this.headers()));
if (err) {
return reject(err);
}
resolve(care.data)
});
}
async farmbotPOST(item, data) {
return new Promise(async (resolve, reject) => {
let pathItem = paths[item];
if (!pathItem) {
pathItem = item.replace(/([a-z])([A-Z])/, (match, a, b, offset, string) => a + '_' + b.toLowerCase())
}
// if(!pathItem)pathItem = item
let [err, care] = await to(axios
.post(`${FARMBOTURL}/${pathItem}`, data, this.headers()));
if (err) {
return reject(err);
}
resolve(care.data)
});
}
/**
* Delete farmbot device from webApi
*/
async deleteDevice() {
return new Promise(async (resolve, reject) => {
let [err, care] = await to(this.farmbotGET('device'))
if (err) {
return reject(
err
);
}
let device = care;
let now = new Date().toISOString();
device.last_saw_api = now;
;[err, care] = await to(this.farmbotPOST('device', device));
;[err, care] = await to(this.farmbotGET('device'))
resolve(care)
});
}
async fixDeviceAPI() {
return new Promise(async (resolve, reject) => {
let [err, care] = await to(this.farmbotGET('device'))
console.log('device', care)
console.log(err)
if (err) {
return reject(
err
);
}
let device = care;
let now = new Date().toISOString();
console.log(now, typeof now)
device.last_saw_api = now;
;[err, care] = await to(this.farmbotPOST('device', device));
console.log(err)
console.log(care)
;[err, care] = await to(this.farmbotGET('device'))
console.log(err)
console.log(care)
resolve(care)
});
}
/** update FarmBot parameters (broker, token, botId) of this instance from local storage */
refreshTokenData() {
let store = (JSON.parse(window.localStorage.getItem('farmbotSimulator')) ||{}).status || {}
let {
broker,
token,
botId
} = store
this.broker = broker
this.token = token
this.botId = botId
}
/**
* remove all listeners
*/
removeAllListeners() {
try {
this.client.end();
} catch (err) { }
}
/** Simulate device uptime. Start/restart clock after successful login */
stopTimerRunTimer() {
try {
this.uptime = 0;
clearInterval(this.timer)
} catch (err) {
}
this.timer = setInterval(() => {
this.uptime++
}, 1000)
}
/**
* connect to MQTT Broker
*
* @returns {Promise} A promise that is resolved with mqtt instance if connection is successful or rejected with the error
*/
async connectMQTT() {
let store = JSON.parse(window.localStorage.getItem('farmbotSimulator')).status
let {
broker,
token,
botId
} = store
broker = `wss://${broker}:443/ws/mqtt`
console.log({
broker,
token,
botId
})
return new Promise((resolve, reject) => {
const client = mqtt.connect(broker, {
clean: true,
// clientId: `FBJS-${Farmbot.VERSION}-${genUuid()}`,
password: token,
protocolId: "MQIsdp",
protocolVersion: 3,
// reconnectPeriod,
username: botId,
});
client.on("error", function (error) {
console.log("Can't connect" + error);
reject(error)
})
client.on("connect", async () => {
client.subscribe(`bot/${this.botId}/#`, {
qos: 1
});
resolve(client)
})
})
}
/**
* Sleep/delay
*
* @param {number} delay_ms - how long to wait in microseconds
* @returns {Promise} A promise that is resolved after the wait period has elapsed
*/
async delay(delay_ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(true)
}, delay_ms)
})
}
/**
* set mqttStatus
*
* @param {boolean} status - mqtt connection status
*/
setMqttStatus(status) {
if (status !== this.mqttStatus) {
this.mqttStatus = status
this.events.emit('mqtt', status ? 'online' : 'offline')
}
}
/**
* Publish ping/pong messages
* @param {number} msgData
* @param {string} msgType - ping/pong
*/
sendPingPong(msgData, msgType) {
this.client.publish(`bot/${this.botId}/${msgType}/${msgData}`, msgData.toString())
}
/**
* update instance with device status: device online/online, other-clients
* @param {boolean} value
* @param {string} status - deviceOnline/otherclients
*/
setDeviceStatus(value, status) {
switch (status) {
case "deviceOnline":
this.deviceOnline = value;
this.emit('status', ` Device: ${value}`)
break;
case "otherClients":
this.otherClients = value;
this.emit('status', ` Other Clients: ${value}`)
// this.emit('status', {
// 'Other Clients': value
// })
break;
default:
;
}
}
/** Monitor device status by monitoring ping/pong messages. Has different instance of mqtt connection */
async scheduleMonitorDeviceStatus() {
let otherClientPings = [];
let thisClientPings = [];
let otherClientPongs = [];
let thisClientPongs = [];
let waitPeriod = 2000;
let pongWaitPeriod = waitPeriod;
let clientMonitorPeriod = 10000;
let mPingPeriod = 2000;
let lastPingTime = 0
let lastPongTime = 0
let lastMPingTime = 0
let pingsFromSelf = []; // [pingMessage]
let pongsFromSelf = []; // [pongMessage]
let allPings = {} // [timePingReceived] = pingMessage
let [err, care] = await to(this.connectMQTT())
if (err) {
throw err
}
let client = care;
setInterval(() => {
let now = new Date().getTime();
if (now - lastPingTime >= waitPeriod) {
pingsFromSelf.push(now)
this.sendPingPong(now, 'ping');
}
}, waitPeriod);
setInterval(() => {
let now = new Date().getTime();
for (let i in allPings) {
let pingTime = parseInt(i)
let msgData = allPings[i];
if (now - pingTime >= pongWaitPeriod) {
pongsFromSelf.push(parseInt(msgData))
this.sendPingPong(msgData, 'pong');
}
}
}, pongWaitPeriod);
setInterval(() => {
if (otherClientPings.length > 0.5 * (thisClientPings.length)) {
this.setDeviceStatus(true, 'otherClients')
} else {
this.setDeviceStatus(false, 'otherClients')
}
if (otherClientPongs.length > 0.5 * (thisClientPongs.length)) {
this.setDeviceStatus(true, 'deviceOnline')
} else {
this.setDeviceStatus(false, 'deviceOnline')
}
thisClientPings = [];
otherClientPings = [];
thisClientPongs = [];
otherClientPongs = [];
}, clientMonitorPeriod);
setInterval(() => {
let now = new Date().getTime()
// this.publishGeneralData(now.toString(), 'mping');
if (now - lastMPingTime > (5 * mPingPeriod)) {
this.setMqttStatus(false);
}
}, mPingPeriod);
console.log('client')
console.log('client')
console.log(client)
console.log('client')
client.on("message", (wholeTopic, message) => {
try {
message = message.toString();
let topic = wholeTopic.split('/')[2];
lastMPingTime = new Date().getTime();
this.setMqttStatus(true)
switch (topic) {
case "ping":
lastPingTime = new Date().getTime()
if (pingsFromSelf.includes(parseInt(message))) {
thisClientPings.push(lastPingTime)
let index = pingsFromSelf.indexOf(message)
pingsFromSelf.splice(index, 1);
} else { // ping is from other client(s)
otherClientPings.push(lastPingTime);
this.sendPingPong(message, 'pong');
pongsFromSelf.push(parseInt(msgData));
}
allPings[lastPingTime] = message
break;
case "pong":
lastPongTime = new Date().getTime()
if (pongsFromSelf.includes(parseInt(message))) {
thisClientPongs.push(lastPongTime)
let index = pongsFromSelf.indexOf(message)
pongsFromSelf.splice(index, 1);
} else {
otherClientPongs.push(lastPongTime);
}
for (let i in allPings) {
let pingMsg = allPings[i];
if (pingMsg === message) {
delete allPings[i];
}
}
break;
}
} catch (err) { }
})
}
// /** Monitor mqtt connection status */
// monitorMQTT() {
// let client = this.client;
// // client.on('disconnect', () => {
// // this.events.emit('mqtt', 'disconnected')
// // })
// // client.on('offline', () => {
// // this.events.emit('mqtt', 'offline')
// // })
// this.on('mqtt', (message) => {
// if (message === 'offline') {
// this.events.emit('status', {
// 'mqtt': false
// })
// }
// if (message === 'online') {
// this.events.emit('status', {
// 'mqtt': true
// })
// }
// console.log('listener:', message)
// })
// }
/**
* publish response from device
* @param {object} args - receivedMessage.args
*/
publishFromDevice(args) {
let message = {
"args": {
"label": args.label
},
"kind": "rpc_ok"
}
this.publishGeneralData(message, 'from_device')
}
/**
* setUserEnv
* @param {Array} body
*/
setUserEnv(body) {
for (let i in body) {
let kind = body[i].kind;
switch (kind) {
case "pair":
let args = body[i].args;
this.userEnv[args.label] = args.value;
break;
}
}
}
/**
* Move relative. Get the number of steps to move per second along each axis.
* @param {Array<number>} start - [x,y,z]
* @param {Array<number>} stop - [x,y,z]
* @param {number} speed
* @returns {object} - {distance, time, [speeds]}
*/
linearPath(start, stop, speed) {
console.log(start, stop, speed)
let index = 0;
let coordinates = start.map(item => {
return [stop[index++], item]
})
let distances = coordinates.map(point => point[0] - point[1]) // retains the sign
let distance = Math.sqrt(distances.map(distance => distance * distance).reduce((acc, currentValue) => acc + currentValue))
let time = distance / speed;
let speeds = distances.map(distance => distance / time)
return {
distance,
time,
speeds
}
}
/**
* Move the bot end effector
* @param {Array<number>} locationData - [x,y,z] || {x,y,z}
*/
setPosition(locationData) {
let [x, y, z] = locationData;
let locationChange = false;
if (x !== this.location.x || y !== this.location.y || z !== this.location.z) {
locationChange = true
}
this.location.x = x
this.location.y = y
this.location.z = z
if (locationChange) {
console.log('LOCATION', [x, y, z])
this.emit('location', [x, y, z])
}
}
/**
* Move the bot end effector
* @param {Object} motionData - { distance, time, speeds}
* @param {Array} newLocation - [x,y,z]
* @returns {Promise} args - args from message received
*/
async moveBot(motionData, newLocation) {
return new Promise((resolve, reject) => {
this.emit('toLocation', newLocation);
return resolve(true)
let {
distance,
time,
speeds
} = motionData;
let startPos = [this.location.x, this.location.y, this.location.z]
let timeElapsed = 0;
let interval = 10;
let minPosDisplacements = [10,10,10] ;
let index = 0;
let directions = newLocation.map(item=>item-startPos[index++]);// <0, 0, >0
let motion = setInterval(() => {
let index = 0;
timeElapsed += interval
let pos = startPos.map(point => point + (speeds[index++] * timeElapsed / 1000))
// if(newLocation[0] pos[0] )
index = 0;
let stopMoving = [false, false, false];
directions.map(item=>{
if(item<0 && pos[index] < newLocation[index]){
pos[index] = newLocation[index]; // negative direction
stopMoving[index] = true
}
else if(item>0 && pos[index] > newLocation[index]){
pos[index] = newLocation[index]; // positive direction
stopMoving[index] = true
} else if (item === 0){
stopMoving[index] = true
pos[index] = newLocation[index];
}else if (pos[index] === newLocation[index])stopMoving[index] = true
index++
if(stopMoving.every(item=>item)){
clearInterval(motion);
this.publishStatusMessage();
resolve(true);
}
})
this.setPosition(pos)
this.publishStatusMessage();
}, interval);
// let stopAfter = time * 1000
// console.log(stopAfter, time, motionData)
// setTimeout(() => {
// clearInterval(motion);
// let index = 0;
// let pos = startPos.map(point => point + (speeds[index++] * stopAfter / 1000));
// this.setPosition(newLocation);
// this.publishStatusMessage();
// resolve(true)
// }, stopAfter)
})
}
/**
* Move relative
* @param {Object} body - {location, offset, speed}
* @param {Object} args - args from message received
*/
async moveRelative(body, args) {
let {
x,
y,
z,
speed
} = body
let relativeCoordinates = [x, y, z]
let logData = {
"channels": [],
"created_at": new Date().getTime().toString().slice(0, 10),
"major_version": 10,
"message": `Moving relative to (${relativeCoordinates[0]},${relativeCoordinates[1]},${relativeCoordinates[2]})`,
"meta": {
"assertion_passed": null,
"assertion_type": null
},
"minor_version": 1,
"patch_version": 4,
"type": "info",
"verbosity": 2,
"x": this.location.x,
"y": this.location.y,
"z": this.location.z
}
console.log(logData);
this.publishGeneralData(logData, 'logs');
let offset = [this.location.x, this.location.y, this.location.z];
let newLocation = [...relativeCoordinates]
let index = 0;
newLocation = newLocation.map(item => offset[index++] + item);
let motionData = this.linearPath(offset, newLocation, speed)
await this.moveBot(motionData, newLocation);
this.publishFromDevice(args);
}
/**
* Move absolute
* @param {Object} body - {location, offset, speed}
* @param {Object} args - args from message received
*/
async moveAbsolute(body, args) {
let offset = body.offset.args;
let newLocation = body.location.args;
let speed = body.speed;
let newLocationCoordinates = [newLocation.x, newLocation.y, newLocation.z]
newLocation = [newLocation.x, newLocation.y, newLocation.z]
offset = [offset.x, offset.y, offset.z]
let localOffset = [this.location.x, this.location.y, this.location.z]
for (let i in offset) {
if (offset[i] !== localOffset[i]) offset[i] = localOffset[i]
}
let logData = {
"channels": [],
"created_at": new Date().getTime().toString().slice(0, 10),
"major_version": 10,
"message": `Moving to (${newLocationCoordinates[0]},${newLocationCoordinates[1]},${newLocationCoordinates[2]})`,
"meta": {
"assertion_passed": null,
"assertion_type": null
},
"minor_version": 1,
"patch_version": 4,
"type": "info",
"verbosity": 2,
"x": this.location.x,
"y": this.location.y,
"z": this.location.z
}
console.log(logData)
this.publishGeneralData(logData, 'logs');
let motionData = this.linearPath(offset, newLocation, speed)
console.log(motionData)
await this.moveBot(motionData, newLocation);
this.publishFromDevice(args);
}
/**
* Process rpc_requests
* @param {Array} messages - array of rpc actions
* @param {Object} args - args from message received
*/
async processRpcRequests(messages, args) {
for (let i in messages) {
let kind = messages[i].kind
switch (kind) {
case "set_user_env":
this.setUserEnv(messages[i].body)
this.publishFromDevice(args);
break;
case "move_absolute":
this.moveAbsolute(messages[i].args, args)
break;
case "move_relative":
this.moveRelative(messages[i].args, args);
break;
}
}
}
/**
* Process messages published to from_clients topic
* @param {string} wholeTopic - mqtt topic
* @param {string} message - received message
*/
async processFromClientTopicMessages(wholeTopic, message) {
try {
message = decodeURIComponent(message);
} catch (error) { }
try {
message = JSON.parse(message);
} catch (error) { }
console.log(message)
let msgType = message.kind;
let body = message.body;
let args = message.args;
console.log(msgType)
console.log(body)
switch (msgType) {
case "rpc_request":
this.processRpcRequests(body, args)
break;
}
}
/**
* Process messages published to sync topic
* @param {string} wholeTopic - mqtt topic
* @param {string} message - received message
*/
processSyncMessages(wholeTopic, message) {
let syncType = wholeTopic.split('/')[3]
switch (syncType) {
case "FarmwareEnv":
this.publishFromDevice(message.args)
break;
case "Device":
this.publishFromDevice(message.args)
this.deviceSync = message.body;
break;
}
}
/** Monitor Messages from FarmBot Client sent to device */
monitorDownlinkMessages() {
let client = this.client;
client.on("message", (wholeTopic, message) => {
try {
// console.log(wholeTopic)
let topic = wholeTopic.split('/')[2];
switch (topic) {
case "from_clients":
this.processFromClientTopicMessages(wholeTopic, message);
break;
case "sync":
this.processSyncMessages(wholeTopic, message);
break;
}
} catch (err) {
}
})
}
/**
* Publish general messages
* @param {string||object} msgData
* @param {string} msgType
*/
publishGeneralData(msgData, msgType) {
if (typeof msgData === 'object') {
msgData = JSON.stringify(msgData);
}
this.client.publish(`bot/${this.botId}/${msgType}`, msgData)
}
/** Schedule publish log messages */
schedulePublishLogs() {
let logData = {
"channels": [],
"created_at": new Date().getTime().toString().slice(0, 10),
"major_version": 10,
"message": "Farmbot is up and running!",
"meta": {
"assertion_passed": null,
"assertion_type": null
},
"minor_version": 1,
"patch_version": 4,
"type": "success",
"verbosity": 1,
"x": this.location.x,
"y": this.location.y,
"z": this.location.z
}
this.publishGeneralData(logData, 'logs');
}
/** Handle telemetry data. Send initial messages (which are not absolutely necessary), then send periodic messages */
schedulePublishTelemetry() {
let initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "interface_configure",
"telemetry.meta": {
"file": "/nerves/build/farmbot_os/platform/target/network.ex",
"function": "{:handle_info, 2}",
"interface": "wlan0",
"line": 140,
"module": "Elixir.FarmbotOS.Platform.Target.Network"
},
"telemetry.subsystem": "network",
"telemetry.uuid": "9ff8173c-5fe2-4b50-b2db-da38ad52ecc4",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "reset",
"telemetry.meta": {
"file": "/nerves/build/farmbot_os/platform/target/network.ex",
"function": "{:reset_ntp, 0}",
"line": 358,
"module": "Elixir.FarmbotOS.Platform.Target.Network"
},
"telemetry.subsystem": "ntp",
"telemetry.uuid": "95ea2925-53e7-4e3c-a4f5-227cefe99eb6",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "interface_connect",
"telemetry.meta": {
"file": "/nerves/build/farmbot_os/platform/target/network.ex",
"function": "{:handle_info, 2}",
"interface": "wlan0",
"line": 181,
"module": "Elixir.FarmbotOS.Platform.Target.Network"
},
"telemetry.subsystem": "network",
"telemetry.uuid": "621e5ee8-0d71-4f79-b5ec-6b67253f5908",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "lan_connect",
"telemetry.meta": {
"file": "/nerves/build/farmbot_os/platform/target/network.ex",
"function": "{:handle_info, 2}",
"interface": "wlan0",
"line": 196,
"module": "Elixir.FarmbotOS.Platform.Target.Network"
},
"telemetry.subsystem": "network",
"telemetry.uuid": "a94eb734-bfa8-4664-8e39-e47aba534c4b",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "connection_open",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/connection_worker.ex",
"function": "{:handle_info, 2}",
"line": 120,
"module": "Elixir.FarmbotExt.AMQP.ConnectionWorker"
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "b0acda3b-8e27-438f-aa36-c65e08a5dc93",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "channel_open",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/log_channel.ex",
"function": "{:handle_info, 2}",
"line": 43,
"module": "Elixir.FarmbotExt.AMQP.LogChannel"
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "7b6b7de5-d2f7-469d-afa6-72b8cd065c1c",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "lan_connect",
"telemetry.meta": {
"file": "/nerves/build/farmbot_os/platform/target/network.ex",
"function": "{:handle_info, 2}",
"interface": "wlan0",
"line": 196,
"module": "Elixir.FarmbotOS.Platform.Target.Network"
},
"telemetry.subsystem": "network",
"telemetry.uuid": "6d150eb5-0c03-46d9-b17d-bd668711af05",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "channel_open",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/telemetry_channel.ex",
"function": "{:handle_info, 2}",
"line": 58,
"module": "Elixir.FarmbotExt.AMQP.TelemetryChannel"
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "550154f8-93ef-4fb4-8efe-72fc9f9f30bb",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "channel_open",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/bot_state_channel.ex",
"function": "{:handle_info, 2}",
"line": 57,
"module": "Elixir.FarmbotExt.AMQP.BotStateChannel"
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "dbace292-522f-4ce5-a0d9-d70869693a1a",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry_captured_at": new Date().toISOString(),
"telemetry_cpu_usage": 6,
"telemetry_disk_usage": 0,
"telemetry_memory_usage": 44,
"telemetry_scheduler_usage": 6,
"telemetry_soc_temp": 50,
"telemetry_target": "rpi3",
"telemetry_throttled": "0x0",
"telemetry_uptime": this.uptime,
"telemetry_wifi_level": -39,
"telemetry_wifi_level_percent": 90
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "channel_open",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/ping_pong_channel.ex",
"function": "{:handle_info, 2}",
"line": 73,
"module": "Elixir.FarmbotExt.AMQP.PingPongChannel"
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "fa9183a8-8829-4f67-a743-45a2bb54c38c",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "channel_open",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/celery_script_channel.ex",
"function": "{:handle_info, 2}",
"line": 53,
"module": "Elixir.FarmbotExt.AMQP.CeleryScriptChannel"
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "97d452cf-e39b-4deb-aad2-7256757aec9a",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "queue_bind",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/celery_script_channel.ex",
"function": "{:handle_info, 2}",
"line": 54,
"module": "Elixir.FarmbotExt.AMQP.CeleryScriptChannel",
"queue_name": `${this.botId}_from_clients`,
"routing_key": `bot.${this.botId}.from_clients`
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "f5b25e2a-5593-4163-bc01-f9c34cb75cf2",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "queue_bind",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/ping_pong_channel.ex",
"function": "{:handle_info, 2}",
"line": 75,
"module": "Elixir.FarmbotExt.AMQP.PingPongChannel",
"queue_name": `${this.botId}_ping`,
"routing_key": `bot.${this.botId}.ping.#`
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "3ca9da32-0812-43e1-a809-482b98708562",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "channel_open",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/connection_worker.ex",
"function": "{:maybe_connect, 4}",
"line": 62,
"module": "Elixir.FarmbotExt.AMQP.ConnectionWorker"
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "2f2988a2-f290-4e1d-b396-8723cd417e81",
"telemetry.value": null
}
this.publishGeneralData(initialTelemetryData, 'telemetry');
initialTelemetryData = {
"telemetry.captured_at": new Date().toISOString(),
"telemetry.kind": "event",
"telemetry.measurement": "queue_bind",
"telemetry.meta": {
"file": "/nerves/build/farmbot_ext/lib/farmbot_ext/amqp/connection_worker.ex",
"function": "{:maybe_connect, 4}",
"line": 63,
"module": "Elixir.FarmbotExt.AMQP.ConnectionWorker",
"queue_name": `${this.botId}_auto_sync`,
"routing_key": `bot.${this.botId}.sync.#`
},
"telemetry.subsystem": "amqp",
"telemetry.uuid": "4d265203-176c-4ff5-8748-4523382a6a00",
"telemetry.value": null
}
setInterval(() => {
let telemetryData = {
"telemetry_captured_at": new Date().toISOString(),
"telemetry_cpu_usage": 2,
"telemetry_disk_usage": 0,
"telemetry_memory_usage": 53,
"telemetry_scheduler_usage": 2,
"telemetry_soc_temp": 41,
"telemetry_target": "rpi3",
"telemetry_throttled": "0x0",
"telemetry_uptime": this.uptime,
"telemetry_wifi_level": -39,
"telemetry_wifi_level_percent": 90
}
this.publishGeneralData(telemetryData, 'telemetry');
}, 300000)
}
/** Publish status message to update device position, etc */
publishStatusMessage() {
let statusData = {
"configuration": {
"arduino_debug_messages": false,
"auto_sync": false,
"beta_opt_in": false,
"disable_factory_reset": false,
"firmware_debug_log": false,
"firmware_hardware": "farmduino_k14",
"firmware_input_log": false,
"firmware_output_log": false,
"network_not_found_timer": null,
"os_auto_update": false,
"sequence_body_log": false,
"sequence_complete_log": false,
"sequence_init_log": false
},
"informational_settings": {
"busy": false,
"cache_bust": null,
"commit": "1c5ef14bfa90cbbaff792f3a14c7c0707c73bb08",
"controller_commit": "1c5ef14bfa90cbbaff792f3a14c7c0707c73bb08",
"controller_uuid": "29417194-a853-55ef-6de8-91dd9b849b0b",
"controller_version": "10.1.4",
"cpu_usage": 3,
"disk_usage": 0,
"env": "prod",
"firmware_commit": "1711db1d9923bc295f81a5eb9952f6b3a10db6a9",
"firmware_version": "6.4.2.G",
"idle": true,
"last_status": null,
"locked": false,
"memory_usage": 60,
"node_name": "farmbot@farmbot-000000004ed75c64.local",
"private_ip": "192.168.100.30",
"scheduler_usage": 3,
"soc_temp": 34,
"sync_status": "sync_now",
"target": "rpi3",
"throttled": "0x0",
"update_available": true,
"uptime": this.uptime,
"wifi_level": -37,
"wifi_level_percent": 91
},
"jobs": {},
"location_data": {
"axis_states": {
"x": "unknown",
"y": "unknown",
"z": "unknown"
},
"load": {
"x": null,
"y": null,
"z": null
},
"position": {
"x": this.location.x,
"y": this.location.y,
"z": this.location.z
},
"raw_encoders": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"scaled_encoders": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
},
"mcu_params": {
"movement_stall_sensitivity_z": 30.0,
"movement_stop_at_max_y": 0.0,
"encoder_missed_steps_max_y": 5.0,
"movement_keep_active_y": 1.0,
"movement_steps_acc_dec_y": 300.0,
"movement_invert_2_endpoints_y": 0.0,
"movement_keep_active_z": 1.0,
"movement_max_spd_y": 400.0,
"pin_guard_5_time_out": 60.0,
"encoder_scaling_z": 5556.0,
"pin_guard_4_pin_nr": 0.0,
"pin_guard_3_time_out": 60.0,
"movement_steps_acc_dec_x": 300.0,
"encoder_missed_steps_decay_z": 5.0,
"movement_home_up_x": 0.0,
"movement_secondary_motor_invert_x": 1.0,
"encoder_enabled_y": 1.0,
"movement_axis_nr_steps_x": 0.0,
"movement_motor_current_x": 600.0,
"movement_timeout_x": 120.0,
"movement_invert_endpoints_x": 0.0,
"movement_home_spd_y": 50.0,
"encoder_enabled_z": 1.0,
"movement_enable_endpoints_z": 0.0,
"movement_home_at_boot_y": 0.0,
"movement_axis_nr_steps_z": 0.0,
"movement_invert_motor_x": 0.0,
"encoder_invert_z": 0.0,
"movement_home_spd_z": 50.0,
"encoder_type_y": 0.0,
"movement_enable_endpoints_y": 0.0,
"pin_guard_3_active_state": 1.0,
"encoder_scaling_y": 5556.0,
"movement_stop_at_max_x": 0.0,
"encoder_missed_steps_decay_x": 5.0,
"movement_timeout_z": 120.0,
"encoder_scaling_x": 5556.0,
"movement_keep_active_x": 1.0,
"movement_min_spd_y": 50.0,
"movement_max_spd_x": 400.0,
"movement_stop_at_max_z": 0.0,
"encoder_missed_steps_max_x": 5.0,
"pin_guard_1_active_state": 1.0,
"movement_home_up_z": 1.0,
"encoder_missed_steps_decay_y": 5.0,
"pin_guard_1_time_out": 60.0,
"movement_step_per_mm_x": 5.0,
"movement_home_at_boot_x": 0.0,
"movement_invert_2_endpoints_z": 0.0,
"movement_home_spd_x": 50.0,
"pin_guard_4_active_state": 1.0,
"movement_stall_sensitivity_x": 30.0,
"encoder_type_x": 0.0,
"movement_min_spd_z": 50.0,
"pin_guard_3_pin_nr": 0.0,
"pin_guard_2_pin_nr": 0.0,
"pin_guard_5_active_state": 1.0,
"pin_guard_2_active_state": 1.0,
"movement_motor_current_y": 600.0,
"movement_home_up_y": 0.0,
"movement_axis_nr_steps_y": 0.0,
"movement_stall_sensitivity_y": 30.0,
"movement_invert_endpoints_z": 0.0,
"movement_home_at_boot_z": 0.0,
"movement_microsteps_y": 1.0,
"pin_guard_1_pin_nr": 0.0,
"movement_invert_motor_z": 0.0,
"pin_guard_4_time_out": 60.0,
"encoder_use_for_pos_x": 0.0,
"pin_guard_5_pin_nr": 0.0,
"encoder_invert_x": 0.0,
"movement_step_per_mm_y": 5.0,
"movement_invert_2_endpoints_x": 0.0,
"encoder_use_for_pos_y": 0.0,
"movement_invert_motor_y": 0.0,
"movement_microsteps_x": 1.0,
"param_mov_nr_retry": 3.0,
"movement_min_spd_x": 50.0,
"movement_invert_endpoints_y": 0.0,
"movement_steps_acc_dec_z": 300.0,
"movement_max_spd_z": 400.0,
"movement_stop_at_home_z": 0.0,
"param_e_stop_on_mov_err": 0.0,
"movement_enable_endpoints_x": 0.0,
"encoder_enabled_x": 1.0,
"movement_microsteps_z": 1.0,
"encoder_missed_steps_max_z": 5.0,
"encoder_invert_y": 0.0,
"pin_guard_2_time_out": 60.0,
"movement_step_per_mm_z": 25.0,
"encoder_type_z": 0.0,
"movement_timeout_y": 120.0,
"movement_secondary_motor_x": 1.0,
"movement_stop_at_home_x": 0.0,
"movement_motor_current_z": 600.0,
"movement_stop_at_home_y": 0.0,
"encoder_use_for_pos_z": 0.0
},
"pins": {},
"process_info": {
"farmwares": {}
},
"user_env": {
"LAST_CLIENT_CONNECTED": this.userEnv.LAST_CLIENT_CONNECTED || "\"2020-08-25T06:28:44.168Z\"",
"camera": "\"USB\""
}
}
this.publishGeneralData(statusData, 'status');
}
/** Publish status message every so often. */
schedulePublishStatusMessage() {
setInterval(() => {
this.publishStatusMessage();
}, 5000)
}
}
export const state = new stateController();