/**
* Roboliq: Automation for liquid-handling robots
* @copyright 2017, ETH Zurich, Ellis Whitehead
* @license GPL-3.0
*/
/**
* Module for a generic 4-site centrifuge.
* @module
*/
const _ = require('lodash');
const assert = require('assert');
const math = require('mathjs');
const commandHelper = require('roboliq-processor/dist/commandHelper.js');
const expect = require('roboliq-processor/dist/expect.js');
const {makeEvowareFacts} = require('roboliq-evoware/dist/equipment/evoware.js');
/**
* @typedef CentrifugeParams
* @type {object}
* @property {!string} evowareId - the Evoware ID of this equipment
* @property {!string} evowareCarrier - the carrier that the equipment is on
* @property {!string} evowareGrid - the grid that the equipment is on
* @param {!Object} sites - keys are the site names (just the base part, without namespace), values are objects with the property `evowareSite`.
* @param {!CentrifugeSitesToModels[]} siteModelCompatibilities - an array of objects {sites, models} of which sites
* @example
* ```
* {
* evowareId: "Centrifuge",
* evowareCarrier: "Centrifuge",
* evowareGrid: 54,
* sites: {
* CENTRIFUGE_1: { evowareSite: 1 },
* CENTRIFUGE_2: { evowareSite: 2 },
* CENTRIFUGE_3: { evowareSite: 1 },
* CENTRIFUGE_4: { evowareSite: 2 }
* },
* siteModelCompatibilities: [
* {
* sites: ["CENTRIFUGE_1", "CENTRIFUGE_2", "CENTRIFUGE_3", "CENTRIFUGE_4"],
* models: ["plateModel_384_square", "plateModel_96_round_transparent_nunc"]
* },
* {
* sites: ["CENTRIFUGE_2", "CENTRIFUGE_4"],
* models: ["plateModel_96_dwp"]
* },
* ]
* }
*/
/**
* @typedef CentrifugeSitesToModels
* @type {object}
* @property {!string[]} sites - array of site names (just the base part, without namespace)
* @property {!string[]} models - array of model names (just the base part, without namespace)
*/
/**
* Configure a generic centrifuge
* @param {CentrifugeParams} params - parameters for the configuration
* @return {EquipmentConfig}
*/
function configure(config, equipmentName, params) {
// console.log("centrifuge4:"); console.log(JSON.stringify(params, null, '\t'))
const N = Object.keys(params.sites).length;
assert(N == 4, "centrifuge4 has only been designed for 4-site centrifuges. Please contact the developer if you need a different number of sites.")
// const sites = _.mapValues(params.sites, value => ({
// evowareCarrier: params.evowareId,
// evowareGrid: params.evowareGrid,
// evowareSite: value.evowareSite,
// close: true
// }));
// Create map from site to all its compatible models
const siteToModels = _(params.siteModelCompatibilities)
.map(x => x.sites.map(site0 => ({site: config.getSiteName(site0), models: x.models.map(config.getModelName)})))
.flatten()
.groupBy(x => x.site)
.mapValues(x => _.flatten(_.map(x, "models")))
.value();
// console.log({siteToModels})
const agent = config.getAgentName();
const equipment = config.getEquipmentName(equipmentName);
const siteNames = Object.keys(siteToModels);
const objects = {};
// Add centrifuge
_.set(objects, equipment, {
type: "Centrifuge",
evowareId: params.evowareId,
sitesInternal: siteNames
});
// Add internal sites
_.forEach(params.sites, (value, key) => {
const site = config.getSiteName(key);
_.set(objects, site, {
type: "Site",
evowareCarrier: params.evowareCarrier,
evowareGrid: params.evowareGrid,
evowareSite: value.evowareSite,
closed: true
});
});
// Add predicates for siteModelCompatibilities
const output = {};
config.addSiteModelCompatibilities(params.siteModelCompatibilities, output);
// console.log({siteModelCompatibilities: params.siteModelCompatibilities, output})
// For the 1st and 3rd sites
const predicates13 = _(siteToModels[siteNames[0]]).map(model => {
return {"centrifuge.canAgentEquipmentModelSite1Site2": {
agent, equipment,
model,
site1: siteNames[0],
site2: siteNames[2],
}}
}).flatten().value();
// For the 2nd and 4th sites
const predicates24 = _(siteToModels[siteNames[1]]).map(model => {
return {"centrifuge.canAgentEquipmentModelSite1Site2": {
agent, equipment,
model,
site1: siteNames[1],
site2: siteNames[3],
}}
}).flatten().value();
// console.log({siteToModels, siteNames, predicates24})
const predicateTasks = _.flatten([
{"action": {"description": equipment+".close: close the centrifuge",
"task": {[equipment+".close"]: {"agent": "?agent", "equipment": "?equipment"}},
"preconditions": [],
"deletions": [],
"additions": siteNames.map(site => ({siteIsClosed: {site}}))
}},
_.map(siteNames, (site, i) => {
return {"method": {"description": "generic.openSite-"+site,
"task": {"generic.openSite": {"site": "?site"}},
"preconditions": [{"same": {"thing1": "?site", "thing2": site}}],
"subtasks": {"ordered": [{[equipment+".open"+(i+1)]: {}}]}
}};
}),
_.map(siteNames, (site, i) => {
return {"action": {"description": equipment+".open"+(i+1)+": open an internal site on the centrifuge",
"task": {[equipment+".open"+(i+1)]: {}},
"preconditions": [],
"deletions": [
{"siteIsClosed": {"site": site}}
],
"additions": _.map(_.without(siteNames, site), function(site2) {
return {"siteIsClosed": {"site": site2}};
})
}};
}),
_.map(siteNames, (site, i) => ({method: {
"description": "generic.closeSite-"+site,
"task": {"generic.closeSite": {"site": "?site"}},
"preconditions": [
{"same": {"thing1": "?site", "thing2": site}}
],
"subtasks": {"ordered": [
{[equipment+".close"]: {"agent": "ourlab.mario.evoware", "equipment": "ourlab.mario.centrifuge"}}
]}
}})),
]);
const planHandlers = {};
planHandlers[equipment+".close"] = (params, parentParams, data) => {
return [{
command: "equipment.close",
agent,
equipment
}];
};
_.forEach(siteNames, (site, i) => {
planHandlers[equipment+".open"+(i+1)] = (params, parentParams, data) => {
return [{
command: "equipment.openSite",
agent,
equipment,
site
}];
};
});
const schemas = {
[`equipment.close|${agent}|${equipment}`]: {
properties: {
agent: {description: "Agent identifier", type: "Agent"},
equipment: {description: "Equipment identifier", type: "Equipment"},
},
required: ["agent", "equipment"]
},
[`equipment.open|${agent}|${equipment}`]: {
properties: {
agent: {description: "Agent identifier", type: "Agent"},
equipment: {description: "Equipment identifier", type: "Equipment"},
},
required: ["agent", "equipment"]
},
[`equipment.openSite|${agent}|${equipment}`]: {
properties: {
agent: {description: "Agent identifier", type: "Agent"},
equipment: {description: "Equipment identifier", type: "Equipment"},
site: {description: "Site identifier", type: "Site"}
},
required: ["agent", "equipment", "site"]
},
[`equipment.run|${agent}|${equipment}`]: {
properties: {
agent: {description: "Agent identifier", type: "Agent"},
equipment: {description: "Equipment identifier", type: "Equipment"},
program: {
description: "Program for centrifuging",
type: "object",
properties: {
rpm: {type: "number", default: 3000},
duration: {type: "Duration", default: "30 s"},
spinUpTime: {type: "Duration", default: "9 s"},
spinDownTime: {type: "Duration", default: "9 s"},
temperature: {type: "Temperature", default: "25 degC"}
}
}
},
required: ["program"]
},
};
const commandHandlers = {
[`equipment.close|${agent}|${equipment}`]: function(params, parsed, data) {
return {expansion: [makeEvowareFacts(parsed, data, "Close")]};
},
[`equipment.open|${agent}|${equipment}`]: function(params, parsed, data) {
return {expansion: [makeEvowareFacts(parsed, data, "Open")]};
},
[`equipment.openSite|${agent}|${equipment}`]: function(params, parsed, data) {
var carrier = commandHelper.getParsedValue(parsed, data, "equipment", "evowareId");
var sitesInternal = commandHelper.getParsedValue(parsed, data, "equipment", "sitesInternal");
var siteIndex = sitesInternal.indexOf(parsed.objectName.site);
expect.truthy({paramName: "site"}, siteIndex >= 0, "site must be one of the equipments internal sites: "+sitesInternal.join(", "));
return {
expansion: [
{
command: "evoware._facts",
agent: parsed.objectName.agent,
factsEquipment: carrier,
factsVariable: carrier+"_MoveToPos",
factsValue: (siteIndex+1).toString()
},
{
command: "evoware._facts",
agent: parsed.objectName.agent,
factsEquipment: carrier,
factsVariable: carrier+"_Open"
},
]
};
},
[`equipment.run|${agent}|${equipment}`]: function(params, parsed, data) {
//console.log("equipment.run|ourlab.mario.evoware|ourlab.mario.centrifuge:")
//console.log({parsed, params})
const parsedProgram = parsed.value.program;
//console.log({parsedProgram});
var list = [
math.round(parsedProgram.rpm),
math.round(parsedProgram.duration.toNumber('s')),
math.round(parsedProgram.spinUpTime.toNumber('s')),
math.round(parsedProgram.spinDownTime.toNumber('s')),
math.round(parsedProgram.temperature.toNumber('degC'))
];
var value = list.join(",");
return {expansion: [makeEvowareFacts(parsed, data, "Execute1", value)]};
},
};
const protocol = {
schemas,
objects,
predicates: _.flatten([output.predicates, predicates13, predicates24, predicateTasks]),
planHandlers,
commandHandlers,
};
// console.log("centrifuge predicates: "+JSON.stringify(protocol.predicates, null, '\t'))
return protocol;
}
module.exports = {
configure
};