Skip to content

Commit

Permalink
Add device attributes to device selectors
Browse files Browse the repository at this point in the history
Attributes are key-value pairs that allow the user to select a device by
name, location, or other characteristic.

Currently, the only allowed attribute is name, of String type.

The AST is more flexible though to support parameter passing (needed
to make primitive templates with device names). $? is not supported
because it's not very useful - Almond natively asks for which
device anyway.
  • Loading branch information
gcampax committed Nov 1, 2019
1 parent e07b3b7 commit f9953a4
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 34 deletions.
13 changes: 9 additions & 4 deletions lib/ast/program.js
Expand Up @@ -20,6 +20,7 @@ const {
recursiveYieldArraySlots,
FieldSlot
} = require('./slots');
const { stringEscape } = require('../escaping');

/**
* Base class of all expressions that select a device.
Expand Down Expand Up @@ -48,10 +49,12 @@ class DeviceSelector extends Selector {
*
* @param {string} kind - the Thingpedia class ID
* @param {string|null} id - the unique ID of the device being selected, or null
* to select all devices
* to select devices according to the attributes, or
* all devices if no attributes are specified
* @param {null} principal - reserved/deprecated, must be `null`
* @param {Ast.InputParam[]} attributes - other attributes used to select a device, if ID is unspecified
*/
constructor(kind, id, principal) {
constructor(kind, id, principal, attributes = []) {
super();

assert(typeof kind === 'string');
Expand All @@ -62,14 +65,16 @@ class DeviceSelector extends Selector {

assert(principal === null);
this.principal = principal;

this.attributes = attributes;
}

clone() {
return new DeviceSelector(this.kind, this.id, this.principal);
return new DeviceSelector(this.kind, this.id, this.principal, this.attributes.slice());
}

toString() {
return `Device(${this.kind}, ${this.id ? this.id : ''}, ${this.principal ? this.principal : ''})`;

}
}
DeviceSelector.prototype.isDevice = true;
Expand Down
32 changes: 13 additions & 19 deletions lib/grammar.pegjs
Expand Up @@ -285,28 +285,22 @@ example = 'stream' _ params:(lambda_param_decl_list)? _ ':=' _ stream:stream _
// Function Calls
device_selector = type:class_name _ '(' _ values:device_attribute_list _ ')' {
var id;
if (values.id !== undefined)
id = values.id;
else
id = null;
if (id !== null)
id = id.toJS();
return new Ast.Selector.Device(type, id, null);
device_selector = type:class_name _ attrlist:input_param_list {
let id = null;
for (let { name, value } of attrlist) {
if (name === 'id') {
if (!value.isString)
return error(`id attribute must be a constant string`);
id = value.toJS();
}
}
return new Ast.Selector.Device(type, id, null, attrlist.filter((attr) => attr.name !== 'id'));
}
device_attribute_list = first:device_attribute _ rest:(',' _ device_attribute _)* {
var obj = {};
obj[first[0]] = first[1];
for (var [name, value] of rest) {
if (obj[name] !== undefined) return error('Duplicate device attribute ' + name);
obj[name] = value;
}
return obj;
return [first].concat(take(rest, 2));
}
device_attribute = device_id
device_id = 'id' _ '=' _ value:string_value {
return ['id', value];
device_attribute = name:('id' / 'name') _ '=' _ value:value {
return new Ast.InputParam(name, value);
}

short_function_name = '@' first_name:classident _ rest_names:('.' _ classident _)+ {
Expand Down
15 changes: 12 additions & 3 deletions lib/prettyprint.js
Expand Up @@ -102,9 +102,18 @@ function prettyprintSelector(ast) {
if (ast.isBuiltin)
return '';

if (ast.id)
return `@${ast.kind}(id=${stringEscape(ast.id)})`;
return `@${ast.kind}`;
if (ast.attributes.length > 0) {
const attributes = ast.attributes.map(prettyprintInputParam).join(', ');

if (ast.id)
return `@${ast.kind}(id=${stringEscape(ast.id)}, ${attributes})`;
else
return `@${ast.kind}(${attributes})`;
} else {
if (ast.id)
return `@${ast.kind}(id=${stringEscape(ast.id)})`;
return `@${ast.kind}`;
}
}

function prettyprintInputParam(ast) {
Expand Down
29 changes: 22 additions & 7 deletions lib/typecheck.js
Expand Up @@ -614,15 +614,30 @@ function resolveJoin(ast, lhs, rhs) {

function typeCheckInputArgs(ast, schema, scope, classes) {
if (!ast.isVarRef && !ast.isJoin) {
if (ast.selector.kind in classes) {
const classdef = classes[ast.selector.kind];
assert(ast.selector);

if (ast.selector.isDevice) {
let dupes = new Set;
for (let attr of ast.selector.attributes) {
if (dupes.has(attr.name))
throw new TypeError(`Duplicate device attribute ${attr.name}`);
dupes.add(attr.name);

const valueType = typeForValue(attr.value, scope);
if (!Type.isAssignable(valueType, Type.String, {}, false))
throw new TypeError(`Invalid type for device attribute ${attr.name}, have ${valueType}, need String`);
}

if (classdef.extends && classdef.extends.length === 1 && classdef.extends[0] === 'org.thingpedia.builtin.thingengine.remote')
ast.__effectiveSelector = new Ast.Selector.Device('org.thingpedia.builtin.thingengine.remote', ast.selector.id, ast.selector.principal);
else
if (ast.selector.kind in classes) {
const classdef = classes[ast.selector.kind];

if (classdef.extends && classdef.extends.length === 1 && classdef.extends[0] === 'org.thingpedia.builtin.thingengine.remote')
ast.__effectiveSelector = new Ast.Selector.Device('org.thingpedia.builtin.thingengine.remote', ast.selector.id, ast.selector.principal, ast.selector.attributes.slice());
else
ast.__effectiveSelector = ast.selector;
} else {
ast.__effectiveSelector = ast.selector;
} else {
ast.__effectiveSelector = ast.selector;
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions test/sample.apps
Expand Up @@ -1935,3 +1935,10 @@ now => compute flat(review) of @org.schema.hotel() => notify;
====

now => compute flat(review, author == 'bob') of @org.schema.hotel() => notify;

====

now => @light-bulb(name="bathroom").set_power(power=enum(on));
now => @light-bulb(name="ceiling").set_power(power=enum(on));

let query x(p_name : String) := @light-bulb(name=p_name).set_power(power=enum(on));
6 changes: 5 additions & 1 deletion test/test_prettyprint.js
Expand Up @@ -46,7 +46,11 @@ const TEST_CASES = [
list query q(out num: Number);
}
now => (@foo.bar.q()), @foo.bar.itself(x=num) >= 0 => notify;
now => (@foo.bar.q()), @for.bar.greaterThanZero(x=num) => notify;`
now => (@foo.bar.q()), @for.bar.greaterThanZero(x=num) => notify;`,

// device selectors
`now => @light-bulb(name="bathroom").set_power(power=enum(on));`,
`now => @light-bulb(id="io.home-assistant/http://hassio.local:8123-light.hue_bloom_1", name="bathroom").set_power(power=enum(on));`
];

function main() {
Expand Down

0 comments on commit f9953a4

Please sign in to comment.