I am experimenting with a node red - VOLTTRON (Python framework) integration where I am hoping to view the VOLTTRON message bus in Node Red.
When I do the appropriate steps as defined in the README like copying the files over to the correct ~/.node-red/nodes/volttron
and getting the correct VOLTTRON authentication keys
When I start Node Red, I get an error:
12 Dec 14:07:54 - [info] Server now running at http://127.0.0.1:8000/
12 Dec 14:07:54 - [info] Starting flows
12 Dec 14:07:54 - [info] Started flows
12 Dec 14:07:54 - [warn] [volttron-input:03074cce8abc3174] /home/ben/.node-red/nodes/volttron
12 Dec 14:07:54 - [warn] [volttron-input:03074cce8abc3174] /home/ben/.node-red/nodes/volttron/node_red_subscriber.py
12 Dec 14:07:54 - [red] Uncaught Exception:
12 Dec 14:07:54 - TypeError: PythonShell is not a constructor
at Timeout._onTimeout (/home/ben/.node-red/nodes/volttron/volttron.js:33:23)
at listOnTimeout (internal/timers.js:554:17)
at processTimers (internal/timers.js:497:7)
I am unsure if this would be a problem associated with javascript or Python? Thanks for any tips next to zero wisdom here.
volttron.js
module.exports = function(RED) {
// Set these variables to be valid file paths
var volttron_env = '/home/ben/.volttron';
var volttron_home = '/home/ben/Desktop/volttron';
var python_path = '/usr/lib/python3.8';
function VolttronInputNode(config) {
RED.nodes.createNode(this,config);
var node = this;
var pyshell = null;
this.on('close', function(done) {
setTimeout(function() {
/* do some asynchronous cleanup before calling done */
if (pyshell && !pyshell.terminated && pyshell.childProcess)
pyshell.childProcess.kill('SIGINT');
done();
});
});
setTimeout(function() {
var PythonShell = require('python-shell');
process.env['VIRTUAL_ENV'] = volttron_env;
process.env['VOLTTRON_HOME'] = volttron_home;
var options = {
mode: 'json',
pythonPath: python_path,
scriptPath: __dirname,
};
var path = require('path');
node.warn(__dirname);
var scriptName = 'node_red_subscriber.py';
var scriptPath = path.resolve(__dirname, scriptName);
node.warn(scriptPath);
pyshell = new PythonShell(scriptName, options);
pyshell.on('message', function (data) {
msg = {};
msg.topic = node.name;
msg.payload = data;
node.send(msg);
});
pyshell.end(function (err) {
node.error(err);
});
});
}
RED.nodes.registerType("volttron-input", VolttronInputNode);
function VolttronOutputNode(config) {
RED.nodes.createNode(this,config);
var node = this;
this.on('close', function(done) {
setTimeout(function() { /* do some asynchronous cleanup before calling done */ done(); });
});
this.on("input", function (msg) {
setTimeout(function() { // send asynchronously
var PythonShell = require('python-shell');
process.env['VIRTUAL_ENV'] = volttron_env;
process.env['VOLTTRON_HOME'] = volttron_home;
var options = {
mode: 'json',
pythonPath: python_path,
scriptPath: __dirname,
args: [msg.payload.topic, msg.payload.data]
};
var path = require('path');
var scriptName = 'node_red_publisher.py';
var scriptPath = path.resolve(__dirname, scriptName);
PythonShell.run(scriptName, options, function(err, result) {
if (err) node.error(err);
if (result) node.warn(result);
});
});
});
}
RED.nodes.registerType("volttron-output",VolttronOutputNode);
}
node_red_subscriber.py
from datetime import datetime
import os
import sys
import gevent
from volttron.platform.messaging import headers as headers_mod
from volttron.platform.vip.agent import Agent, PubSub, Core
from volttron.platform.agent import utils
from volttron.platform.scheduling import periodic
from volttron.platform import jsonapi
from settings import topic_prefixes_to_watch, heartbeat_period, agent_kwargs
class NodeRedSubscriber(Agent):
def onmessage(self, peer, sender, bus, topic, headers, message):
d = {'topic': topic,
'headers': headers,
'message': message}
sys.stdout.write(jsonapi.dumps(d)+'\n')
sys.stdout.flush()
@Core.receiver('onstart')
def onstart(self, sender, **kwargs):
for prefix in topic_prefixes_to_watch:
self.vip.pubsub.subscribe(peer='pubsub', prefix=prefix, callback=self.onmessage).get(timeout=10)
# Demonstrate periodic decorator and settings access
@Core.schedule(periodic(heartbeat_period))
def publish_heartbeat(self):
now = utils.format_timestamp(datetime.utcnow())
headers = {
headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT,
headers_mod.DATE: now,
headers_mod.TIMESTAMP: now
}
result = self.vip.pubsub.publish('pubsub', 'heartbeat/NodeRedSubscriber', headers, now)
result.get(timeout=10)
if __name__ == '__main__':
try:
# If stdout is a pipe, re-open it line buffered
if utils.isapipe(sys.stdout):
# Hold a reference to the previous file object so it doesn't
# get garbage collected and close the underlying descriptor.
stdout = sys.stdout
sys.stdout = os.fdopen(stdout.fileno(), 'w', 1)
agent = NodeRedSubscriber(identity='NodeRedSubscriber', **agent_kwargs)
task = gevent.spawn(agent.core.run)
try:
task.join()
finally:
task.kill()
except KeyboardInterrupt:
pass
EDIT
Trying some steps further, in the directory .node-red/node_modules
I placed one file called package.json
with the contents provided in the answer with the dependency "python-shell": "^1.0.4"
as provided in the answer. And then from this same directory running from bash some npm errors come up:
~/.node-red/node_modules$ npm install volttron
traceback:
npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/volttron - Not found
npm ERR! 404
npm ERR! 404 'volttron@*' is not in this registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.
npm ERR! A complete log of this run can be found in:
npm ERR! /home/ben/.npm/_logs/2021-12-13T12_52_57_550Z-debug.log
In the other directory ~/.node-red/nodes/volttron
These files are present:
node_red_publisher.py node_red_subscriber.py README settings.py volttron.html volttron.js
In the previous answer I had to guess which version of python-shell
was used by the node as it has no hints, so I picked the current latest version (3.0.1) as an arbitrary choice.
It appears that this was the wrong choice soI suggest you edit the package.json
file again and change the ^3.0.1
version for the python-shell
dependency and change it to ^1.0.4
{
"name" : "volttron",
"version" : "0.0.1",
"description" : "A sample node for node-red",
"dependencies": {
"python-shell": "^1.0.4"
},
"keywords": [ "node-red" ],
"node-red" : {
"nodes": {
"volttron": "volttron.js"
}
}
}
You will need to run npm install
again in the volttron
directory after making the change.
If this is not the case then you will have to take this up with Volttron.