window.Rt0s = class Rt0s { constructor(url, app, uid, pw, onChangeState) { this.sublist = {}; this.apis = {}; this.reqs = {}; this.req_inds = {}; this.sreqs = {} this.req_seq = 0 this.connected = false; this.connected_reported = false; this.onChangeState = onChangeState; this.client = null; this.current_user = ''; this.current_user_stamp = 0; this.visitorId = "?" this._url = url this._app = app this._uid = uid this._pw = pw this.token = "" //this.dut = "dut2" this.dut = "tif" if (true) { const fpPromise = import('https://openfpcdn.io/fingerprintjs/v3') .then(FingerprintJS => FingerprintJS.load()) fpPromise .then(fp => fp.get()) .then(result => { this.cpu_id = result.visitorId this._cid = result.visitorId + "_" + (Rt0s.stamp().toString()+ ":" +this._app ) this.do_connect(this._uid, this._pw) }) } else this.do_connect(this._uid, this._pw) this.registerAPI('ping', "pings", [], (msg) => { console.log('WE WERE PINGED - AND PONGED BACK'); return { pong: true }; }); this.registerAPI("api", "Get API", [], (msg) => { var ret = [] for (var c of Object.keys(this.apis)) { ret.push({ cmd: c, descr: this.apis[c].descr, args: this.apis[c].args, }) } return ret; }) setInterval(() => { if ((this.current_user == "anon" || this.current_user == "")) { return; } for (var k in this.reqs) { var r = this.reqs[k]; const now = Rt0s.stamp(); if (r.done) delete this.reqs[k]; else if (now > r.sent + r.timeout * r.tries) { if (r.retries > r.tries) { r.tries += 1; r.obj.resend = r.tries; if (r.logger) r.logger(`R${r.req_seq}:${r.tries}/${r.retries}:${r.obj.target}:${JSON.stringify(r.obj.req.args)}`, 'yellow') this.publish(`/dn/${r.obj.target}/${r.obj.mid}`, r.obj); r.sent = now; } else { if (r.logger) r.logger(`T${r.req_seq}:${r.tries}/${r.retries}:${r.obj.target}:${JSON.stringify(r.obj.req.args)}`, 'red') r.err('timeout'); r.done = true; } } } }, 100); } static dates = (d) => { if (!d || d == 0) return "" now = stamp() oset = (new Date).getTimezoneOffset(); dd = new Date(d - oset * 60 * 1000).toISOString() if (now - d < 24 * 60 * 60 * 1000) return dd.slice(11, 19); return dd.slice(0, 19); } static timed(d) { var dd = d || 0 if (dd < 60 * 60) return sprintf("%d:%02d", dd / 60, dd % 60) else return sprintf("%d:%02d:%02d", dd / 3600, (dd / 60) % 60, dd % 60) } static hide_id(id) { var e = document.getElementById(id) if (e) { e.style.display = "none" } } static show_id(id, style) { var e = document.getElementById(id) if (e) { e.style.display = style ? style : "block" } } static $(sel) { switch (sel.substring(0, 1)) { case '#': return document.getElementById(sel.substring(1)) } return "" } static async get_file(fn) { return new Promise((res) => { fetch(fn) .then((resp) => { return resp.text() }) .then(function(data) { res(data) }) }); } static async get_binary_file(fn) { return new Promise((res) => { fetch(fn) .then((resp) => { return resp.arrayBuffer() }) .then(function(data) { res(data) }) }); } static uuidv4() { var result = ''; for (var j = 0; j < 32; j++) { if (j == 8 || j == 12 || j == 16 || j == 20) result = result + '-'; result = result + Math.floor(Math.random() * 16).toString(16).toUpperCase(); } return result; }; /* async rt0s_read(size, address, data_len) { //await Rt0s.sleep(10) var verb if (size == "B") verb = "read8" else if (size == "W") verb = "read16" else if (size == "L") verb = "read32" else return [] var ret = await mq.req(this.dut, [verb, { address, data_len }], {}) if (ret && "data" in ret) return ret['data'] else return [] } async rt0s_write(size, address, data) { //await Rt0s.sleep(10) var verb if (size == "B") verb = "write8" else if (size == "W") verb = "write16" else if (size == "L") verb = "write32" else return false var ret = await mq.req(this.dut, [verb, { address, data_len: data.length, data:data }], {}) console.log("rt0s_write",size,address,data,ret); return ret } */ static async sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); }; static stamp() { return new Date().getTime(); }; static stamps() { return new Date().getTime() / 1000; }; static match(filter, topic) { const filterArray = filter.split('/'); const length = filterArray.length; const topicArray = topic.split('/'); for (var i = 0; i < length; ++i) { var left = filterArray[i]; var right = topicArray[i]; if (left === '#') return topicArray.length >= length - 1; if (left !== '+' && left !== right) return false; } return length === topicArray.length; }; change_state(newstate) { if (this.connected == newstate && newstate == false) { this.client.end(true, () => { delete this.client; this.client = {}; log_red("** MQTT Client Deleted"); }); return; } if (newstate && this.current_user_stamp == 0) this.current_user_stamp = Rt0s.stamp() if (this.connected == newstate) return; this.connected = newstate; if (newstate) { Rt0s.sleep(50).then(() => { if (this.connected != this.connected_reported) { this.onChangeState(this.connected); this.connected_reported = this.connected; } }); } else if (this.connected != this.connected_reported) { this.onChangeState(this.connected); this.connected_reported = this.connected; } } end() { // if (this.client) // this.client.end(true); //this.change_state(false); }; reconnect() { client.reconnect(); }; send_ind(path, obj, options = {}) { try { this.client.publish(`/ind/${this._cid}/${path}`, JSON.stringify(obj), options); } catch (error) { console.error('ERR broadcast:', error); } }; req_ind(src, topic, name, cb) { var key = src + "_" + topic var path = "/ind/" + src + "/" + topic // if (!(this.req_inds[name])) { // this.req_inds[name] = [] // } var len = this.req_inds[name] = { 'src': src, 'topic': topic, 'key': key, path, 'cb': cb, } //if (len == 1) // first of the kind => need to subs this.client.subscribe(path) //return len - 1; // return position } unreq_ind(src, topic, name) { var key = src + "_" + topic console.log("unreq_ind",src,topic,key,name,this.req_inds); if (!(this.req_inds[name])) return // no ind => nothing to do delete (this.req_inds[name]) } publish(path, obj, options = {}) { try { this.client.publish(path, JSON.stringify(obj), options); } catch (error) { console.error('ERR publish:', error); } }; subscribe(topic, cb) { this.client.subscribe( topic, function(err) { if (err) { console.error('ERR subscribe:', err); } else { if (cb) this.sublist[topic] = cb; } }.bind(this) ); }; registerAPI(path, descr, args, cb) { this.apis[path] = { "f": cb, descr, args, }; } async req(target, msg, options, logger) { return new Promise((ok, err) => { if ((this.current_user == "anon" || this.current_user == "") && target != "manager") { console.log("no reqs for anon", target, this.current_user); ok({ results: [] }) return } var obj = { mid: Rt0s.uuidv4(), src: this._cid, target: target, req: { args: msg }, token: this.token, }; this.req_seq += 1 this.reqs[obj['mid']] = { obj: obj, logger, ok: ok, err: err, done: false, created: Rt0s.stamp(), sent: Rt0s.stamp(), tries: 1, retries: 'retries' in options ? options.tries : 3, timeout: 'timeout' in options ? options.timeout : 3000, req_seq: this.req_seq, } if (this.reqs[obj['mid']].logger) this.reqs[obj['mid']].logger(`S${this.reqs[obj['mid']].req_seq}:1/${this.reqs[obj['mid']].retries}:${target}:${JSON.stringify(obj.req.args)}`, 'white') this.publish(`/dn/${target}/${obj['mid']}`, obj); }) }; do_subs() { this.subscribe(`/up/${this._cid}/+`, (topic, obj) => { if (obj['mid'] in this.reqs) { var r = this.reqs[obj['mid']]; if (r.logger) r.logger(`R${r.req_seq}:${r.tries}/${r.retries}:${r.obj.target}:${JSON.stringify(obj.reply)}`, 'lightgreen') r.done = true; r.ok(obj.reply); } else { hit = Rt0s.match(/\/up\/(.+)\/(.+)/, topic) if (hit) { if (hit[2] in sreqs) { sreqs[hit[2]](hit[2], obj) } } } }); this.subscribe(`/dn/${this._cid}/+`, (topic, msg) => { if (msg['req']['args'][0] in this.apis) { var api = this.apis[msg['req']['args'][0]]; var reply = api['f'](msg); if (reply == null) { console.log('api will reply later'); return; } msg['reply'] = reply; } else if ('*' in this.apis) { var api = this.apis['*']; var reply = api['f'](msg); if (reply == null) { return; } msg['reply'] = reply; } else msg['reply'] = { error: `no api '${msg['req']['args'][0]}'`, }; this.publish(`/up/${msg['src']}/${msg['mid']}`, msg); return; }); }; do_connect(_uid, _pw) { this.current_user = _uid this.current_user_stamp = 0 if (this.connected) this.end() this.client = mqtt.connect(this._url, { reconnectPeriod: 10000, clean: true, username: _uid, password: _pw, clientId: this._cid, }); this.onChangeState(this.connected); this.client.on('connect', function() { this.change_state(true); if (this.current_user != "anon") this.send_ind("state", { "state": "online", "stamp": Rt0s.stamp() }, { retain: false, qos: 2 }); this.do_subs(); }.bind(this)); this.client.on('disconnect', function(err) { log_red("** MQTT Disconnected"); this.change_state(false); }.bind(this)); this.client.on('offline', function(err) { log_red("** MQTT Offline"); this.change_state(false); }.bind(this)); this.client.on( 'message', function(topic, msg) { for (var key of Object.keys(this.req_inds)) { var req_ind = this.req_inds[key] //console.log("chkxxx", key, req_ind,Object.keys(this.req_inds)); var ind = req_ind if (Rt0s.match(ind['path'], topic)) { var obj = JSON.parse(msg.toString()); var p = topic.split("/"); obj.device = p[2] obj.indication = p[3] obj.received = Rt0s.stamp(); ind['cb'](ind, obj); } } Object.keys(this.sublist).forEach(sub => { if (Rt0s.match(sub, topic)) { try { var obj = JSON.parse(msg.toString()); obj.received = Rt0s.stamp(); this.sublist[sub](topic, obj); } catch (error) { console.log('bad handler', topic, this.sublist, msg.toString()); } } }); }.bind(this) ); }; }