Source: commands/timer.js

/**
 * 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
};