Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actionlib configuration - first pass. #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const rosnodejs = require('rosnodejs');
const ActionServerInterface = require('rosnodejs/dist/lib/ActionServerInterface');
const ActionClientInterface = require('rosnodejs/dist/lib/ActionClientInterface');

const ActionLib = require('./index.js');

ActionLib.config({
time: rosnodejs.Time,
log: rosnodejs.log.getLogger('actionlibjs'),
messages: {
getMessage(fullName) {
const [pkg, name] = fullName.split('/');
return rosnodejs.require(pkg).msg[name]
},
getMessageConstants(fullName) {
return this.getMessage(fullName).CONSTANTS;
}
},
ActionServerInterface,
ActionClientInterface
});

rosnodejs.initNode('/tmp')
.then(() => {
const as = new ActionLib.ActionServer({
type: 'intera_motion_msgs/MotionCommand',
actionServer: '/motion',
nh: rosnodejs.nh
});


})
.catch((err) => {
console.error(err.stack);
})
26 changes: 23 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
const ActionServer = require('./action/ActionServer.js');
const ActionServer = require('./lib/ActionServer.js');
const ActionConfig = require('./lib/ActionConfig')

module.exports = {
ActionServer
const ActionLib = {
config(configuration) {
ActionConfig.init(configuration);
}
};

function addGuardedGetter(propertyName, val) {
Object.defineProperty(ActionLib, propertyName, {
get: function() {
if (ActionConfig.isConfigured()) {
return val;
}
else {
throw new Error(`Unable to get propertyName before actionlib has been configured`);
}
}
});
}

addGuardedGetter('ActionServer', ActionServer);

module.exports = ActionLib;
88 changes: 88 additions & 0 deletions lib/ActionConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@

const EventEmitter = require('events').EventEmitter;

let ACTION_CONF = {};
let configured = false;

// make this so conf can alert when its configured
const exportItem = new EventEmitter();

const FIELDS_FORMAT = [
'log',
{
name: 'time',
format: [
'isZeroTime',
'timeComp',
'now',
'toNumber',
'epoch'
]
},
{
name: 'messages',
format: [
'getMessage',
'getMessageConstants'
]
},
'ActionServerInterface',
'ActionClientInterface'
];

function reset() {
configured = false;
}

function readConfig(conf, formatFields, writeConf, keyPath = '') {
if (!formatFields) {
throw new Error('Unable to readConfig without format at ' + keyPath);
}
else if (!conf) {
throw new Error('Invalid config - missing entry at ' + keyPath);
}

for (let i = 0; i < formatFields.length; ++i) {
const formatField = formatFields[i];

if (typeof formatField === 'string') {
const confVal = conf[formatField];
if (confVal === undefined) {
const fullPath = keyPath ? keyPath + '.' + formatField : formatField;
throw new Error('Unable to readConfig without field ' + fullPath);
}

writeConf[formatField] = confVal;
}
else if (typeof formatField === 'object') {
const { name, format } = formatField;
if (!writeConf.hasOwnProperty(name)) {
writeConf[name] = {};
}

const fullPath = keyPath ? keyPath + '.' + name : name;
readConfig(conf[name], format, writeConf[name], fullPath);
}
}
}

function init(conf) {
readConfig(conf, FIELDS_FORMAT, ACTION_CONF);
configured = true;
exportItem.emit('configured', ACTION_CONF);
}

function isConfigured() {
return configured;
}

function get() {
return ACTION_CONF;
}

exportItem.init = init;
exportItem.reset = reset;
exportItem.isConfigured = isConfigured;
exportItem.get = get;

module.exports = exportItem;
78 changes: 43 additions & 35 deletions actions/ActionServer.js → lib/ActionServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,12 @@

'use strict';

const timeUtils = require('../lib/Time.js');
const msgUtils = require('../utils/message_utils.js');
const ActionConfig = require('./ActionConfig.js');

const EventEmitter = require('events');

const ActionServerInterface = require('../lib/ActionServerInterface.js');
const GoalHandle = require('./GoalHandle.js');

let GoalIdMsg = null;
let GoalStatusMsg = null;
let GoalStatusArrayMsg = null;
let GoalStatuses = null;
let goalCount = 0;

/**
* @class ActionServer
* EXPERIMENTAL
Expand All @@ -39,28 +32,7 @@ class ActionServer extends EventEmitter {
constructor(options) {
super();

if (GoalStatusMsg === null) {
GoalStatusMsg = msgUtils.requireMsgPackage('actionlib_msgs').msg.GoalStatus;
GoalStatuses = GoalStatusMsg.Constants;
}

if (GoalStatusArrayMsg === null) {
GoalStatusArrayMsg = msgUtils.requireMsgPackage('actionlib_msgs').msg.GoalStatusArray;
}

this._asInterface = new ActionServerInterface(options);

this._asInterface.on('goal', this._handleGoal.bind(this));
this._asInterface.on('cancel', this._handleCancel.bind(this));

const actionType = this._asInterface.getType();

this._messageTypes = {
result: msgUtils.getHandlerForMsgType(actionType + 'Result'),
feedback: msgUtils.getHandlerForMsgType(actionType + 'Feedback'),
actionResult: msgUtils.getHandlerForMsgType(actionType + 'ActionResult'),
actionFeedback: msgUtils.getHandlerForMsgType(actionType + 'ActionFeedback')
};
this._options = options;

this._pubSeqs = {
result: 0,
Expand All @@ -76,6 +48,22 @@ class ActionServer extends EventEmitter {
this._statusListTimeout = 5;
}

start() {
this._asInterface = new ActionServerInterface(this._options);

this._asInterface.on('goal', this._handleGoal.bind(this));
this._asInterface.on('cancel', this._handleCancel.bind(this));

const actionType = this._asInterface.getType();

this._messageTypes = {
result: ActionConfig.get().messages.getMessage(actionType + 'Result'),
feedback: ActionConfig.get().messages.getMessage(actionType + 'Feedback'),
actionResult: ActionConfig.get().messages.getMessage(actionType + 'ActionResult'),
actionFeedback: ActionConfig.get().messages.getMessage(actionType + 'ActionFeedback')
};
}

generateGoalId() {
return this._asInterface.generateGoalId();
}
Expand All @@ -94,9 +82,9 @@ class ActionServer extends EventEmitter {
let handle = this._getGoalHandle(newGoalId);

if (handle) {
if (handle.status === GoalStatuses.RECALLING) {
handle.status = GoalStatuses.RECALLED;
this.publishResult(status.status, this._createMessage('result'));
// check if we already received a request to cancel this goal
if (handle.getStatusId() === GoalStatuses.RECALLING) {
handle.setCancelled(this._createMessage('result'));
}

handle._destructionTime = msg.goal_id.stamp;
Expand Down Expand Up @@ -134,7 +122,7 @@ class ActionServer extends EventEmitter {
for (let i = 0, len = this._goalHandleList.length; i < len; ++i) {
const handle = this._goalHandleList[i];
const handleId = handle.id;
const handleStamp = handle.status.goal_id.stamp;
const handleStamp = handle.getStatus().goal_id.stamp;

if (shouldCancelEverything ||
cancelId === handleId ||
Expand Down Expand Up @@ -226,3 +214,23 @@ class ActionServer extends EventEmitter {
}

module.exports = ActionServer;


//------------------------------------------------------------------------
// Hook into configuration
//------------------------------------------------------------------------

let GoalStatusMsg, GoalStatuses, GoalIdMsg, GoalStatusArrayMsg;
let timeUtils, log, ActionServerInterface;

ActionConfig.on('configured', function(config) {
timeUtils = config.time;
log = config.log;

GoalStatusMsg = config.messages.getMessage('actionlib_msgs/GoalStatus');
GoalStatuses = config.messages.getMessageConstants('actionlib_msgs/GoalStatus');
GoalIdMsg = config.messages.getMessage('actionlib_msgs/GoalId');
GoalStatusArrayMsg = config.messages.getMessage('actionlib_msgs/GoalStatusArray');

ActionServerInterface = config.ActionServerInterface;
});
49 changes: 29 additions & 20 deletions actions/GoalHandle.js → lib/GoalHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,10 @@

'use strict';

const msgUtils = require('../utils/message_utils.js');
const timeUtils = require('../lib/Time.js');
const log = require('../lib/Logging.js').getLogger('ros.rosnodejs');

let GoalStatus = null;
let GoalStatuses = null;
const ActionConfig = require('./ActionConfig.js');

class GoalHandle {
constructor(goalId, actionServer, status) {
constructor(goalId, actionServer, status, goal) {
if (goalId.id === '') {
goalId = actionServer.generateGoalId();
}
Expand All @@ -38,27 +33,18 @@ class GoalHandle {

this._as = actionServer;

if (GoalStatus === null) {
GoalStatus = msgUtils.requireMsgPackage('actionlib_msgs').msg.GoalStatus;
GoalStatuses = GoalStatus.Constants;
}

this._status = new GoalStatus({
status: status || GoalStatuses.PENDING,
goal_id: goalId
});

this._goal = goal;

this._destructionTime = timeUtils.epoch();
}

_isTerminalState() {
return [
GoalStatuses.REJECTED,
GoalStatuses.RECALLED,
GoalStatuses.PREEMPTED,
GoalStatuses.ABORTED,
GoalStatuses.SUCCEEDED
].includes(this._status.status);
getGoal() {
return this._goal;
}

getStatusId() {
Expand Down Expand Up @@ -190,6 +176,29 @@ class GoalHandle {
_logInvalidTransition(transition, currentStatus) {
log.warn('Unable to %s from status %s for goal %s', transition, currentStatus, this.id);
}

_isTerminalState() {
return [
GoalStatuses.REJECTED,
GoalStatuses.RECALLED,
GoalStatuses.PREEMPTED,
GoalStatuses.ABORTED,
GoalStatuses.SUCCEEDED
].includes(this._status.status);
}
}

module.exports = GoalHandle;

//------------------------------------------------------------------------
// Hook into configuration
//------------------------------------------------------------------------

let timeUtils, log, GoalStatus, GoalStatuses;

ActionConfig.on('configured', function(config) {
timeUtils = config.time;
log = config.log;
GoalStatus = config.messages.getMessage('actionlib_msgs/GoalStatus');
GoalStatuses = config.messages.getMessageConstants('actionlib_msgs/GoalStatus');
});
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "chris smith",
"license": "Apache-2.0"
"license": "Apache-2.0",
"devDependencies": {
"chai": "^4.1.2",
"mocha": "^5.0.4",
"rosnodejs": "^2.2.0"
}
}
29 changes: 29 additions & 0 deletions test/actionServerTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

const net = require('net');
const chai = require('chai');
const expect = chai.expect;

const rosnodejs = require('rosnodejs');

const ActionLib = require('../index.js');

ActionLib.config({
time: rosnodejs.Time,
log: rosnodejs.log.getLogger('actionlibjs'),
messages: {
getMessage(fullName) {
const [pkg, name] = fullName.split('/');
return rosnodejs.require(pkg).msg[name]
},
getMessageConstants(fullName) {
return this.getMessage(fullName).CONSTANTS;
}
},
ActionServerInterface,
ActionClientInterface
});

describe('action server', function() {

});