/**
* Roboliq: Automation for liquid-handling robots
* @copyright 2017, ETH Zurich, Ellis Whitehead
* @license GPL-3.0
*/
/**
* Namespace for the ``timer`` commands.
* @namespace timer
* @version v1
*/
/**
* Timer commands module.
* @module commands/timer
* @return {Protocol}
*/
var _ = require('lodash');
var jmespath = require('jmespath');
var yaml = require('yamljs');
var commandHelper = require('../commandHelper.js');
var expect = require('../expect.js');
var misc = require('../misc.js');
/**
* Create predicates for objects of type = "Timer"
* @static
*/
var objectToPredicateConverters = {
"Timer": function(name, object) {
return _.compact([
{"isTimer": {"equipment": name}},
(object.running) ? {"running": {"equipment": name}} : null
]);
},
};
function findAgentEquipmentAlternatives(params, data, running) {
var llpl = require('../HTN/llpl.js').create();
//console.log("predicates:\n"+JSON.stringify(data.predicates))
llpl.initializeDatabase(data.predicates);
var agent = params.agent || "?agent";
var equipment = params.equipment || "?equipment";
var query1 = {"timer.canAgentEquipment": {
"agent": agent,
"equipment": equipment
}};
var query2 =
(running === true) ? {running: {equipment: equipment}}
: (running === false) ? {not: {running: {equipment: equipment}}}
: null;
var query = {"and": _.compact([query1, query2])};
//console.log("query:\n"+JSON.stringify(query, null, '\t'))
var resultList = llpl.query(query);
//console.log("resultList:\n"+JSON.stringify(resultList))
var alternatives = jmespath.search(resultList, '[].and[]."timer.canAgentEquipment"');
if (_.isEmpty(alternatives)) {
var resultList1 = llpl.query(query1);
if (_.isEmpty(resultList1)) {
return {
errors: ["missing timer data (please add predicates `timer.canAgentEquipment`)"]
};
} else {
return {
errors: ["missing available timer configuration for " + JSON.stringify(query)]
};
}
}
return alternatives;
}
/**
* Handlers for {@link timer} commands.
* @static
*/
var commandHandlers = {
/**
* Sleep for a given duration using a specific timer.
*
* Handler should return `effects` that the timer is not running.
*
* @typedef _sleep
* @memberof timer
* @property {string} command - "timer._sleep"
* @property {string} agent - Agent identifier
* @property {string} equipment - Equipment identifier
* @property {number} duration - Number of seconds to sleep
*/
"timer._sleep": function(params, parsed, data) {
var effects = {};
if (parsed.value.stop)
effects[parsed.objectName.equipment + ".running"] = false;
return {
effects: effects
};
},
/**
* Start the given timer.
*
* Handler should return `effects` that the timer is running.
*
* @typedef _start
* @memberof timer
* @property {string} command - "timer._start"
* @property {string} agent - Agent identifier
* @property {string} equipment - Equipment identifier
*/
"timer._start": function(params, parsed, data) {
var effects = {};
effects[parsed.objectName.equipment + ".running"] = true;
return {
effects: effects
};
},
/**
* Stop the given timer.
*
* Handler should return `effects` that the timer is not running.
*
* @typedef _stop
* @memberof timer
* @property {string} command - "timer._stop"
* @property {string} agent - Agent identifier
* @property {string} equipment - Equipment identifier
*/
"timer._stop": function(params, parsed, data) {
var effects = {};
effects[parsed.objectName.equipment + ".running"] = false;
return {
effects: effects
};
},
/**
* Wait until the given timer has reacher the given elapsed time.
*
* Handler should:
* - expect that the timer (identified by the `equipment` parameter) is running
* - return `effects` that the timer is not running
*
* @typedef _wait
* @memberof timer
* @property {string} command - "timer._wait"
* @property {string} agent - Agent identifier
* @property {string} equipment - Equipment identifier
* @property {number} till - Number of seconds to wait till from the time the timer was started
* @property {boolean} stop - Whether to stop the timer after waiting, or let it continue
*/
"timer._wait": function(params, parsed, data) {
// TODO: assert that timer is running
var effects = {};
if (parsed.value.stop)
effects[parsed.objectName.equipment + ".running"] = false;
return {
effects: effects
};
},
/**
* A control construct to perform the given sub-steps and then wait
* until a certain amount of time has elapsed since the beginning of this command.
*
* @typedef doAndWait
* @memberof timer
* @property {string} command - "timer.doAndWait"
* @property {string} [agent] - Agent identifier
* @property {string} [equipment] - Equipment identifier
* @property {number} duration - Number of seconds this command should last
* @property {Array|Object} steps - Sub-steps to perform
*/
"timer.doAndWait": function(params, parsed, data) {
var alternatives = findAgentEquipmentAlternatives(params, data, false);
if (alternatives.errors) return alternatives;
var agent = alternatives[0].agent;
var equipment = alternatives[0].equipment;
var expansion = {
1: {
command: "timer._start",
agent: agent,
equipment: equipment
},
2: parsed.value.steps,
3: {
command: "timer._wait",
agent: agent,
equipment: equipment,
till: parsed.value.duration.toNumber('s'),
stop: true
},
};
return {
expansion: expansion
};
},
/**
* Sleep for a given duration.
*
* @typedef sleep
* @memberof timer
* @property {string} command - "timer.sleep"
* @property {string} [agent] - Agent identifier
* @property {string} [equipment] - Equipment identifier
* @property {number} duration - Number of seconds to sleep
*/
"timer.sleep": function(params, parsed, data) {
var alternatives = findAgentEquipmentAlternatives(params, data, false);
if (alternatives.errors) return alternatives;
var params2 = _.merge(
{
command: "timer._sleep",
duration: parsed.value.duration.format({precision: 6})
},
alternatives[0]
);
var expansion = {
"1": params2
};
// Create the effets object
var effects = {};
//effects[params2.equipment + ".running"] = true;
return {
expansion: expansion,
effects: effects,
alternatives: alternatives
};
},
/**
* Start a timer.
*
* @typedef start
* @memberof timer
* @property {string} command - "timer.start"
* @property {string} [agent] - Agent identifier
* @property {string} [equipment] - Equipment identifier
*/
"timer.start": function(params, parsed, data) {
var alternatives = findAgentEquipmentAlternatives(params, data, false);
//console.log({alternatives})
if (alternatives.errors) return alternatives;
var params2 = _.merge(
{
command: "timer._start"
},
alternatives[0]
);
var expansion = {
"1": params2
};
// Create the effets object
var effects = {};
//effects[params2.equipment + ".running"] = true;
return {
expansion: expansion,
effects: effects,
alternatives: alternatives
};
},
/**
* Stop a running timer.
*
* @typedef stop
* @memberof timer
* @property {string} command - "timer.stop"
* @property {string} [agent] - Agent identifier
* @property {string} [equipment] - Equipment identifier
*/
"timer.stop": function(params, parsed, data) {
var alternatives = findAgentEquipmentAlternatives(params, data, true);
if (alternatives.errors) return alternatives;
if (alternatives.length > 1) {
return {errors: ["ambiguous time.stop command, multiple running timers: "+alternatives]};
}
var params2 = _.merge(
{
command: "timer._stop"
},
alternatives[0]
);
var expansion = {
"1": params2
};
// Create the effets object
var effects = {};
//effects[params2.equipment + ".running"] = false;
return {
expansion: expansion,
effects: effects,
alternatives: alternatives
};
},
"timer.wait": function(params, parsed, data) {
var alternatives = findAgentEquipmentAlternatives(params, data, true);
if (alternatives.errors) return alternatives;
if (alternatives.length > 1) {
return {errors: ["ambiguous time.wait command, multiple running timers: "+alternatives]};
}
var params2 = _.merge(
{
command: "timer._wait",
till: parsed.value.till.format({precision: 14}),
stop: parsed.value.stop
},
alternatives[0]
);
var expansion = {
"1": params2
};
// Create the effets object
var effects = {};
//effects[params2.equipment + ".running"] = false;
return {
expansion: expansion,
effects: effects,
alternatives: alternatives
};
},
};
/**
* @type {Protocol}
*/
module.exports = {
roboliq: "v1",
objectToPredicateConverters,
schemas: yaml.load(__dirname+'/../schemas/timer.yaml'),
commandHandlers
};