diff --git a/src/data/mqtt.ts b/src/data/mqtt.ts new file mode 100644 index 0000000000..c90fc05fc3 --- /dev/null +++ b/src/data/mqtt.ts @@ -0,0 +1,19 @@ +import { HomeAssistant } from "../types"; + +export interface MQTTMessage { + topic: string; + payload: string; + qos: number; + retain: number; +} + +export const subscribeMQTTTopic = ( + hass: HomeAssistant, + topic: string, + callback: (message: MQTTMessage) => void +) => { + return hass.connection.subscribeMessage(callback, { + type: "mqtt/subscribe", + topic, + }); +}; diff --git a/src/panels/developer-tools/event/event-subscribe-card.ts b/src/panels/developer-tools/event/event-subscribe-card.ts index 4b56290f86..d1973b9ffe 100644 --- a/src/panels/developer-tools/event/event-subscribe-card.ts +++ b/src/panels/developer-tools/event/event-subscribe-card.ts @@ -18,9 +18,13 @@ import format_time from "../../../common/datetime/format_time"; @customElement("event-subscribe-card") class EventSubscribeCard extends LitElement { @property() public hass?: HomeAssistant; + @property() private _eventType = ""; + @property() private _subscribed?: () => void; + @property() private _events: Array<{ id: number; event: HassEvent }> = []; + private _eventCount = 0; public disconnectedCallback() { @@ -33,7 +37,7 @@ class EventSubscribeCard extends LitElement { protected render(): TemplateResult { return html` - +
- :host { - -ms-user-select: initial; - -webkit-user-select: initial; - -moz-user-select: initial; - } - - .content { - padding: 24px 0 32px; - max-width: 600px; - margin: 0 auto; - direction: ltr; - } - - mwc-button { - background-color: white; - } - - - - - - - -
- -
- - - -
-
- Publish -
-
-
- `; - } - - static get properties() { - return { - hass: Object, - topic: String, - payload: String, - }; - } - - _publish() { - this.hass.callService("mqtt", "publish", { - topic: this.topic, - payload_template: this.payload, - }); - } -} - -customElements.define("developer-tools-mqtt", HaPanelDevMqtt); diff --git a/src/panels/developer-tools/mqtt/developer-tools-mqtt.ts b/src/panels/developer-tools/mqtt/developer-tools-mqtt.ts new file mode 100644 index 0000000000..3320eb52c3 --- /dev/null +++ b/src/panels/developer-tools/mqtt/developer-tools-mqtt.ts @@ -0,0 +1,126 @@ +import { + LitElement, + customElement, + TemplateResult, + html, + property, + CSSResultArray, + css, +} from "lit-element"; +import "@material/mwc-button"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-input/paper-textarea"; + +import { HomeAssistant } from "../../../types"; + +import { haStyle } from "../../../resources/styles"; +import "../../../components/ha-card"; +import "./mqtt-subscribe-card"; + +@customElement("developer-tools-mqtt") +class HaPanelDevMqtt extends LitElement { + @property() public hass?: HomeAssistant; + + @property() private topic = ""; + + @property() private payload = ""; + + private inited: boolean = false; + + protected firstUpdated() { + if (localStorage && localStorage["panel-dev-mqtt-topic"]) { + this.topic = localStorage["panel-dev-mqtt-topic"]; + } + if (localStorage && localStorage["panel-dev-mqtt-payload"]) { + this.payload = localStorage["panel-dev-mqtt-payload"]; + } + this.inited = true; + } + + protected render(): TemplateResult { + return html` +
+ +
+ + + +
+
+ Publish +
+
+ + +
+ `; + } + + private _handleTopic(ev: CustomEvent) { + this.topic = ev.detail.value; + if (localStorage && this.inited) { + localStorage["panel-dev-mqtt-topic"] = this.topic; + } + } + + private _handlePayload(ev: CustomEvent) { + this.payload = ev.detail.value; + if (localStorage && this.inited) { + localStorage["panel-dev-mqtt-payload"] = this.payload; + } + } + + private _publish(): void { + if (!this.hass) { + return; + } + this.hass.callService("mqtt", "publish", { + topic: this.topic, + payload_template: this.payload, + }); + } + + static get styles(): CSSResultArray { + return [ + haStyle, + css` + :host { + -ms-user-select: initial; + -webkit-user-select: initial; + -moz-user-select: initial; + } + + .content { + padding: 24px 0 32px; + max-width: 600px; + margin: 0 auto; + direction: ltr; + } + + mwc-button { + background-color: white; + } + + mqtt-subscribe-card { + display: block; + margin: 16px auto; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "developer-tools-mqtt": HaPanelDevMqtt; + } +} diff --git a/src/panels/developer-tools/mqtt/mqtt-subscribe-card.ts b/src/panels/developer-tools/mqtt/mqtt-subscribe-card.ts new file mode 100644 index 0000000000..0795fcad30 --- /dev/null +++ b/src/panels/developer-tools/mqtt/mqtt-subscribe-card.ts @@ -0,0 +1,153 @@ +import { + LitElement, + customElement, + TemplateResult, + html, + property, + CSSResult, + css, +} from "lit-element"; +import "@material/mwc-button"; +import "@polymer/paper-input/paper-input"; +import { HomeAssistant } from "../../../types"; +import "../../../components/ha-card"; +import format_time from "../../../common/datetime/format_time"; + +import { subscribeMQTTTopic, MQTTMessage } from "../../../data/mqtt"; + +@customElement("mqtt-subscribe-card") +class MqttSubscribeCard extends LitElement { + @property() public hass?: HomeAssistant; + + @property() private _topic = ""; + + @property() private _subscribed?: () => void; + + @property() private _messages: Array<{ + id: number; + message: MQTTMessage; + payload: string; + time: Date; + }> = []; + + private _messageCount = 0; + + public disconnectedCallback() { + super.disconnectedCallback(); + if (this._subscribed) { + this._subscribed(); + this._subscribed = undefined; + } + } + + protected render(): TemplateResult { + return html` + + + + + ${this._subscribed ? "Stop listening" : "Start listening"} + + +
+ ${this._messages.map( + (msg) => html` +
+ Message ${msg.id} received on ${msg.message.topic} at + ${format_time(msg.time, this.hass!.language)}: +
${msg.payload}
+
+ QoS: ${msg.message.qos} - Retain: + ${Boolean(msg.message.retain)} +
+
+ ` + )} +
+
+ `; + } + + private _valueChanged(ev: CustomEvent): void { + this._topic = ev.detail.value; + } + + private async _handleSubmit(): Promise { + if (this._subscribed) { + this._subscribed(); + this._subscribed = undefined; + } else { + this._subscribed = await subscribeMQTTTopic( + this.hass!, + this._topic, + (message) => this._handleMessage(message) + ); + } + } + + private _handleMessage(message: MQTTMessage) { + const tail = + this._messages.length > 30 ? this._messages.slice(0, 29) : this._messages; + let payload: string; + try { + payload = JSON.stringify(JSON.parse(message.payload), null, 4); + } catch (e) { + payload = message.payload; + } + this._messages = [ + { + payload, + message, + time: new Date(), + id: this._messageCount++, + }, + ...tail, + ]; + } + + static get styles(): CSSResult { + return css` + form { + display: block; + padding: 16px; + } + paper-input { + display: inline-block; + width: 200px; + } + .events { + margin: -16px 0; + padding: 0 16px; + } + .event { + border-bottom: 1px solid var(--divider-color); + padding-bottom: 16px; + margin: 16px 0; + } + .event:last-child { + border-bottom: 0; + } + .bottom { + font-size: 80%; + color: var(--secondary-text-color); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "mqtt-subscribe-card": MqttSubscribeCard; + } +}