Alex2Node/dist/cjs/compat/AlexaStatusMessage.js
David 6789a1a077 bridge: publish through a Publisher; devices no longer hold the broker client
src/transport.ts has the Publisher interface, MqttPublisher (the client the bridge has at the time) and
MemoryPublisher (tests without a broker); src/topics.ts names the four topics the library publishes to.
Device, AlexaStatusMessage, AlexaErrorResponse and sendSceneResponse shared three copies of the
publish-and-report code: they now call one send() that resolves the topic or "" and never rejects.
registerDevice() and addDevice() work before connect(); a send() without a connection resolves "" and the
"error" event says to call connect(). unregisterDevice() and clearDevices() take the publisher from the device.
Device.setMqttClient() is gone, the first constructor argument of Device is ignored, and the message classes
take a Publisher where they took the client. Tests: 108 -> 113.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 19:34:39 +00:00

152 lines
7.7 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlexaStatusMessage = void 0;
const crypto_1 = require("crypto");
const build_js_1 = require("../messages/build.js");
const StateBuilder_js_1 = require("../messages/StateBuilder.js");
const topics = __importStar(require("../topics.js"));
const transport_js_1 = require("../transport.js");
const enums_js_1 = require("./enums.js");
// The 1.x helpers report every temperature in Celsius, whatever scale the device works in
function celsius(scale, value) {
return { value: scale === enums_js_1.TemperatureSensorScale.FAHRENHEIT ? (value - 32) * (5 / 9) : value, scale: "CELSIUS" };
}
/**
* A Response, StateReport, DeferredResponse or ChangeReport as 1.x builds it: device.getStatusMessage() or
* device.getChangeReport(), the add...Prop() calls, send(). The values are not checked.
*/
class AlexaStatusMessage {
constructor(correlationToken, rootTopic, endpointId, publisher, isResponse = false, isDeferred = false, changeCause = null) {
this.correlationToken = correlationToken;
this.rootTopic = rootTopic;
this.endpointId = endpointId;
this.publisher = publisher;
this.isResponse = isResponse;
this.isDeferred = isDeferred;
this.changeCause = changeCause;
// One id for the message, however often toJSON() is called
this.messageId = (0, crypto_1.randomUUID)();
this.state = new StateBuilder_js_1.StateBuilder({ target: changeCause ? "change" : "context" });
}
addProperty(namespace, name, value, uncertaintyInMilliseconds = 0, instance) {
this.state.setRaw(namespace, name, value, { instance, uncertaintyInMilliseconds });
return this;
}
/** ChangeReport: the add*Prop calls that follow describe what CHANGED (the default for a change report). */
changed() {
this.state.changed();
return this;
}
/** ChangeReport: the add*Prop calls that follow describe the other, unchanged properties (context). */
unchanged() {
this.state.unchanged();
return this;
}
/** True for a ChangeReport (Device.getChangeReport). */
isChangeReport() {
return this.changeCause !== null;
}
/**
* The message as it will be published (for tests and logging). A DeferredResponse has no context. Throws
* MessageError for a ChangeReport without a changed property.
*/
toJSON() {
const { endpointId, messageId, correlationToken } = this;
if (this.changeCause) {
return (0, build_js_1.changeReport)({ endpointId, messageId, cause: this.changeCause, changed: this.state.change, context: this.state.context });
}
if (this.isDeferred) {
return (0, build_js_1.deferredResponse)({ endpointId, messageId, correlationToken, estimatedDeferralInSeconds: this.estimatedDeferralInSeconds });
}
const name = this.isResponse ? "Response" : "StateReport";
return (0, build_js_1.response)({ name, endpointId, messageId, correlationToken, context: this.state.context });
}
addModeControllerProp(instance, value, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.MODE_CONTROLLER, "mode", value, uncertaintyInMs, instance);
}
addThermostatModeProp(mode, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.THERMOSTAT_CONTROLLER, "thermostatMode", mode, uncertaintyInMs);
}
addEstimatedDeferralTime(seconds) {
if (this.isDeferred) {
this.estimatedDeferralInSeconds = seconds;
}
else {
console.warn("[AlexaStatusMessage.ts] Attempted to add estimated deferral time, but message is not marked as DeferredResponse.");
}
return this;
}
addThermostatControllerProp(name, scale, value, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.THERMOSTAT_CONTROLLER, name, celsius(scale, value), uncertaintyInMs);
}
/** Alexa.EndpointHealth connectivity: EndpointHealth.OK / UNREACHABLE (the plain strings "OK" / "UNREACHABLE" are accepted too). */
addHealthProp(health, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.ENDPOINT_HEALTH, "connectivity", { value: health }, uncertaintyInMs);
}
addPowerControllerProp(power, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.POWER_CONTROLLER, "powerState", power, uncertaintyInMs);
}
addTemperatureSensorProp(scale, value, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.TEMPERATURE_SENSOR, "temperature", celsius(scale, value), uncertaintyInMs);
}
addBrightnessControllerProp(brightness, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.BRIGHTNESS_CONTROLLER, "brightness", brightness, uncertaintyInMs);
}
addColorTemperatureControllerProp(colorTemp, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.COLOR_TEMPERATURE_CONTROLLER, "colorTemperatureInKelvin", colorTemp, uncertaintyInMs);
}
addToggleControllerProp(state, instance, uncertaintyInMs = 0) {
return this.addProperty(enums_js_1.AlexaInterfaceType.TOGGLE_CONTROLLER, "toggleState", state, uncertaintyInMs, instance);
}
/** A property built by the caller. It goes to the context, also in a ChangeReport after changed(). */
addContextProp(prop) {
this.state.add(prop, "context");
return this;
}
/**
* Publish: a Response/StateReport to <root>/<endpoint>/alexaResponce (sendAsync: deferredResponse), a ChangeReport
* to <root>/changeReport (Alex2MQTT adds the user's token and posts it to the Alexa event gateway). Resolves with
* the topic, or "" when nothing was published: the publish failed, or the message is a ChangeReport without a
* changed property, which Alex2MQTT would drop. The error then goes to the bridge's "error" event (when listened
* to). Never rejects (1.5.1 did, so an un-caught send() could kill the host).
*/
send(sendAsync = false) {
const answer = sendAsync ? topics.deferred : topics.response;
const topic = this.changeCause ? topics.changeReport(this.rootTopic) : answer(this.rootTopic, this.endpointId);
return (0, transport_js_1.send)(this.publisher, topic, () => this.toJSON(), this.onPublishError);
}
}
exports.AlexaStatusMessage = AlexaStatusMessage;