// A roller blind: Alexa.RangeController with the position in percent, 0 closed and 100 open. The semantics say // what "open", "close", "raise" and "lower" do, and which positions count as open and as closed. // // ALEX2MQTT_USERNAME=... ALEX2MQTT_PASSWORD=... ALEX2MQTT_ROOT_TOPIC=... node examples/blind.js // // "alex2node" is the installed package; inside this checkout it resolves to the built dist/. const { Alex2MQTT, RangeController, asset, text, semantics } = require("alex2node"); const missing = ["ALEX2MQTT_USERNAME", "ALEX2MQTT_PASSWORD", "ALEX2MQTT_ROOT_TOPIC"].filter((name) => !process.env[name]); if (missing.length > 0) { console.error(`${missing.join(", ")} not set. Set the MQTT user name, the password and the root topic of your Alex2MQTT account.`); process.exit(1); } const { ALEX2MQTT_USERNAME, ALEX2MQTT_PASSWORD, ALEX2MQTT_ROOT_TOPIC } = process.env; const bridge = new Alex2MQTT(ALEX2MQTT_USERNAME, ALEX2MQTT_PASSWORD, ALEX2MQTT_ROOT_TOPIC); bridge.on("connect", () => console.log("connected, discover the devices in the Alexa app")); bridge.on("error", (err) => console.error("bridge:", err.message)); const blind = { position: 0 }; const device = bridge.addDevice({ endpointId: "bedroom-blind", name: "Bedroom Blind", categories: ["INTERIOR_BLIND"] }); const lift = device.add(RangeController, { instance: "Blind.Lift", friendlyNames: [asset("Alexa.Setting.Opening"), text("Position", "en-US")], range: { min: 0, max: 100, precision: 1 }, unit: "Alexa.Unit.Percent", semantics: semantics() .action("Close", "SetRangeValue", { rangeValue: 0 }) .action("Open", "SetRangeValue", { rangeValue: 100 }) .action("Lower", "AdjustRangeValue", { rangeValueDelta: -10, rangeValueDeltaDefault: false }) .action("Raise", "AdjustRangeValue", { rangeValueDelta: 10, rangeValueDeltaDefault: false }) .state("Closed", 0) .stateRange("Open", 1, 100), }); // lift carries the instance: the property is reported under "Blind.Lift" device.state((s) => s.set(lift, "rangeValue", blind.position).health("OK")); const within = (position) => Math.min(100, Math.max(0, Math.round(position))); lift.on("SetRangeValue", (ctx) => { blind.position = within(ctx.payload.rangeValue); return ctx.respond(); }); // rangeValueDeltaDefault: the user named no amount, and the blind picks its own step lift.on("AdjustRangeValue", (ctx) => { const { rangeValueDelta, rangeValueDeltaDefault } = ctx.payload; const delta = rangeValueDeltaDefault ? Math.sign(rangeValueDelta) * 10 : rangeValueDelta; blind.position = within(blind.position + delta); return ctx.respond(); }); bridge.connect();