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