mqtt.mjsannotatedmqtt.mjssource129 lines · 4.2 KB · raw
1/*
2mqtt.mjs - for patterning the internet of things from strudel
3Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/serial/serial.mjs>
4This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
5*/
6
7import { Pattern, isPattern, createParams } from '@strudel/core';
8import Paho from 'paho-mqtt';
9
10const connections = {};
11
12// Handle connection loss
13function onConnectionLost(responseObject) {
14  if (responseObject.errorCode !== 0) {
15    console.error(' mqtt connection lost: ', responseObject.errorMessage);
16  }
17}
18
19// Handle received messages
20function onMessageArrived(message) {
21  console.log('incoming mqtt message: ', message.payloadString); // prettier-ignore
22}
23
24function onFailure(err) {
25  console.error('Connection failed: ', err);
26  if (typeof window !== 'undefined') {
27    document.cookie = 'mqtt_pass=';
28  }
29}
30
31Pattern.prototype.mqtt = function (
32  username = undefined,
33  password = undefined,
34  topic = undefined,
35  host = 'wss://localhost:8883/',
36  client = undefined,
37  latency = 0,
38  add_meta = true,
39) {
40  const key = host + '-' + client;
41  let password_entered = false;
42
43  function onConnect() {
44    console.log('Connected to mqtt broker');
45    if (password_entered) {
46      document.cookie = 'mqtt_pass=' + password;
47    }
48  }
49
50  let cx;
51  if (connections[key]) {
52    cx = connections[key];
53  } else {
54    if (!client) {
55      client = 'strudel-' + String(Math.floor(Math.random() * 1000000));
56    }
57    cx = new Paho.Client(host, client);
58    connections[key] = cx;
59    cx.onConnectionLost = onConnectionLost;
60    cx.onMessageArrived = onMessageArrived;
61    const props = {
62      onSuccess: onConnect,
63      onFailure: onFailure,
64    };
65
66    if (username) {
67      props.userName = username;
68      if (typeof password === 'undefined' && typeof window !== 'undefined') {
69        const cookie = /mqtt_pass=(\w+)/.exec(window.document.cookie);
70        if (cookie) {
71          password = cookie[1];
72        }
73        if (typeof password === 'undefined') {
74          password = prompt('Please enter MQTT server password');
75          password_entered = true;
76        }
77      }
78
79      props.password = password;
80    }
81    cx.connect(props);
82  }
83  return this.withHap((hap) => {
84    const onTrigger = (hap, currentTime, cps, targetTime) => {
85      let msg_topic = topic;
86      if (!cx || !cx.isConnected()) {
87        return;
88      }
89      let message = '';
90      if (typeof hap.value === 'object') {
91        let value = hap.value;
92
93        // Try to take topic from pattern if it's not set
94        if (typeof msg_topic === 'undefined' && 'topic' in value) {
95          msg_topic = value.topic;
96          if (Array.isArray(msg_topic)) {
97            msg_topic = msg_topic.join('/');
98          }
99          msg_topic = '/' + msg_topic;
100        }
101        if (add_meta) {
102          const duration = hap.duration.div(cps);
103          value = { ...value, duration: duration.valueOf(), cps: cps };
104        }
105        message = JSON.stringify(value);
106      } else {
107        message = hap.value;
108      }
109      message = new Paho.Message(message);
110      message.destinationName = msg_topic;
111
112      const offset = (targetTime - currentTime + latency) * 1000;
113
114      window.setTimeout(function () {
115        cx.send(message);
116      }, offset);
117    };
118    return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
119  });
120};
121
122// This adds the 'move' and 'motor' commands to strudel
123export const { move, motor } = createParams('move', 'motor');
124window.move = move;
125window.motor = motor;
126// This adds the 'robot' command
127Pattern.prototype.robot = function (robot_id, address = 'ws://192.168.8.248:9001/mqtt') {
128  return this.mqtt(undefined, undefined, '/move/' + robot_id, address);
129};