From 03c1a19a94b95a3781ea5f7ee35dab61f6190a2e Mon Sep 17 00:00:00 2001 From: Jason Chen Date: Mon, 22 Feb 2021 18:19:12 +0800 Subject: [PATCH 01/30] new dan2 and new dai profile --- src/context.js | 25 ++ src/dai.js | 79 +++++++ src/dan2.js | 518 +++++++++++++++++++++++++----------------- src/device-feature.js | 16 ++ 4 files changed, 424 insertions(+), 214 deletions(-) create mode 100644 src/context.js create mode 100644 src/dai.js create mode 100644 src/device-feature.js diff --git a/src/context.js b/src/context.js new file mode 100644 index 0000000..43cbada --- /dev/null +++ b/src/context.js @@ -0,0 +1,25 @@ +import ChannelPool from './channel-pool.js' + +export default class { + + constructor() { + this.url = null; + this.app_id = null; + this.name = null; + this.mqtt_host = null; + this.mqtt_port = null; + this.mqtt_username = null; + this.mqtt_password = null; + this.mqtt_client = null; + this.i_chans = new ChannelPool(); + this.o_chans = new ChannelPool(); + this.rev = null; + this.on_signal = null; + this.on_data = null; + this.on_register = null; + this.on_deregister = null; + this.on_connect = null; + this.on_disconnect = null; + // this._mqueue = queue.Queue() # storing the MQTTMessageInfo from ``publish`` + } +} \ No newline at end of file diff --git a/src/dai.js b/src/dai.js new file mode 100644 index 0000000..71c55ad --- /dev/null +++ b/src/dai.js @@ -0,0 +1,79 @@ +import DeviceFeature from './device-feature.js' + +export const dai = function (profile, ida) { + var df_func = {}; + + let api_url = profile['api_url']; + let device_model = profile['device_model']; + let device_addr = profile['device_addr']; + let device_name = profile['device_name']; + let persistent_binding = profile['persistent_binding']; + let username = profile['username']; + let extra_setup_webpage = profile['extra_setup_webpage']; + let device_webpage = profile['device_webpage']; + + let register_callback = profile['register_callback']; + let on_register = profile['on_register']; + let on_deregister = profile['on_deregister']; + let on_connect = profile['on_connect']; + let on_disconnect = profile['on_disconnect']; + + let push_interval = profile['push_interval']; + let interval = profile['interval'] ? profile['interval'] : {}; + + let device_features = {}; + let flags = {}; + + for (let i = 0; i < profile.idf_list.length; i++) { + df_func[profile['idf_list'][i].name] = profile['idf_list'][i]; + profile['idf_list'][i] = profile['idf_list'][i].name; + } + for (let i = 0; i < profile.odf_list.length; i++) { + df_func[profile.odf_list[i].name] = profile.odf_list[i]; + profile.odf_list[i] = profile.odf_list[i].name; + } + + function on_data(odf_name, data) { + df_func[odf_name](data); + } + + function on_signal(cmd, param) { + console.log('[cmd]', cmd, param); + return true; + } + + function init_callback(result) { + console.log('register:', result); + document.title = device_name; + ida.ida_init(); + } + + let msg = { + 'on_signal': on_signal, + 'on_data': on_data, + 'accept_protos': ['mqtt'], + 'id': device_addr, + 'idf_list': profile['idf_list'], + 'odf_list': profile['odf_list'], + 'name': device_name, + 'profile': { + 'model': device_model, + 'u_name': username, + 'extra_setup_webpage': extra_setup_webpage, + 'device_webpage': device_webpage, + }, + 'register_callback': register_callback, + 'on_register': on_register, + 'on_deregister': on_deregister, + 'on_connect': on_connect, + // 'on_disconnect' : f + } + + console.log(msg); + + dan2.register(api_url, msg, init_callback); +}; + +const parse_df_profile = function (profile, typ) { + let t = `${typ}_list`; +} \ No newline at end of file diff --git a/src/dan2.js b/src/dan2.js index e8b2b68..1c22b12 100644 --- a/src/dan2.js +++ b/src/dan2.js @@ -1,244 +1,334 @@ -import ChannelPool from './channel-pool.js' +import Context from './context.js' import _UUID from './uuid.js' import mqtt from 'mqtt' import superagent from 'superagent' -let _url; -let _id; -let _mqtt_host; -let _mqtt_port; -let _mqtt_scheme; -let _mqtt_client; -let _i_chans; -let _o_chans; -let _ctrl_i; -let _ctrl_o; -let _on_signal; -let _on_data; -let _rev; - -const publish = function(channel, message, retained, qos) { - if (!_mqtt_client) - { - console.warn('unable to publish without _mqtt_client'); - return; - } - if (retained === undefined) - retained = false; - if (qos === undefined) - qos = 2; - - _mqtt_client.publish(channel, message, { - retain: retained, - qos: qos, - }); +let ctx; +let _is_reconnect = false; + +const publish = function (channel, message, retained, qos) { + if (!ctx.mqtt_client) { + console.warn('unable to publish without ctx.mqtt_client'); + return; + } + if (retained === undefined) + retained = false; + + if (qos === undefined) + qos = 2; + + ctx.mqtt_client.publish(channel, message, { + retain: retained, + qos: qos, + }); } -const subscribe = function(channel, qos) { - if (!_mqtt_client) - return; - if (qos === undefined) - qos = 2; - return _mqtt_client.subscribe(channel, {qos: qos}); +const subscribe = function (channel, qos) { + if (!ctx.mqtt_client) + return; + if (qos === undefined) + qos = 2; + return ctx.mqtt_client.subscribe(channel, { qos: qos }); } -const unsubscribe = function(channel) { - if (!_mqtt_client) - return; - return _mqtt_client.unsubscribe(channel); +const unsubscribe = function (channel) { + if (!ctx.mqtt_client) + return; + return ctx.mqtt_client.unsubscribe(channel); +} + +const on_connect = function () { + if (!_is_reconnect) { + console.log('Successfully connect to %s', ctx.url); + console.log('Device ID: %s.', ctx.app_id); + console.log('Device name: %s.', ctx.name); + subscribe(ctx.o_chans['ctrl']); + } + else { + console.info('Reconnect: %s.', ctx.name); + publish( + ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), + true // retained message + ); + // for (const k in ctx.o_chans) { + // if (typeof ctx.o_chans[k] != 'object') { + // subscribe(ctx.o_chans[k]); + // } + // else { + // for (const topic in ctx.o_chans[k]) { + // subscribe(topic); + // } + // } + // } + } + // ctx.i_chans.remove_all_df(); + // ctx.o_chans.remove_all_df(); + + publish( + ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'online', 'rev': ctx.rev }), + true // retained message + ); + + _is_reconnect = true; + + if (ctx.on_connect != null) { + ctx.on_connect(); + } } -const on_message = function(topic, message) { - if (topic == _ctrl_o) { - let signal = JSON.parse(message); - let handling_result = null; - switch (signal['command']) { - case 'CONNECT': - if ('idf' in signal) { - let idf = signal['idf']; - _i_chans.add(idf, signal['topic']); - handling_result = _on_signal(signal['command'], [idf]); - - } else if ('odf' in signal) { - let odf = signal['odf']; - _o_chans.add(odf, signal['topic']); - handling_result = _on_signal(signal['command'], [odf]); - subscribe(_o_chans.topic(odf)); +const on_message = function (topic, message) { + if (topic == ctx.o_chans['ctrl']) { + let signal = JSON.parse(message); + let handling_result = null; + switch (signal['command']) { + case 'CONNECT': + if ('idf' in signal) { + let idf = signal['idf']; + ctx.i_chans.add(idf, signal['topic']); + handling_result = ctx.on_signal(signal['command'], [idf]); + + } else if ('odf' in signal) { + let odf = signal['odf']; + ctx.o_chans.add(odf, signal['topic']); + handling_result = ctx.on_signal(signal['command'], [odf]); + subscribe(ctx.o_chans.topic(odf)); + } + break; + case 'DISCONNECT': + if ('idf' in signal) { + let idf = signal['idf']; + ctx.i_chans.remove_df(idf); + handling_result = ctx.on_signal(signal['command'], [idf]); + + } else if ('odf' in signal) { + let odf = signal['odf']; + unsubscribe(ctx.o_chans.topic(odf)); + ctx.o_chans.remove_df(odf); + handling_result = ctx.on_signal(signal['command'], [odf]); + } + break; } - break; - case 'DISCONNECT': - if ('idf' in signal) { - let idf = signal['idf']; - _i_chans.remove_df(idf); - handling_result = _on_signal(signal['command'], [idf]); - - } else if ('odf' in signal) { - let odf = signal['odf']; - unsubscribe(_o_chans.topic(odf)); - _o_chans.remove_df(odf); - handling_result = _on_signal(signal['command'], [odf]); + let res_message = { + 'msg_id': signal['msg_id'], } - break; - } - let res_message = { - 'msg_id': signal['msg_id'], - } - if (typeof handling_result == 'boolean' && handling_result) { - res_message['state'] = 'ok'; - } else { - res_message['state'] = 'error'; - res_message['reason'] = handling_result[1]; - } - publish(_ctrl_i, JSON.stringify(res_message)); - return; - } - else { - let odf = _o_chans.df(topic); - if (!odf) - return; - _on_data(odf, JSON.parse(message)); - } + if (typeof handling_result == 'boolean' && handling_result) { + res_message['state'] = 'ok'; + } else { + res_message['state'] = 'error'; + res_message['reason'] = handling_result[1]; + } + publish(ctx.i_chans['ctrl'], JSON.stringify(res_message)); + return; + } + else { + let odf = ctx.o_chans.df(topic); + if (!odf) + return; + ctx.on_data(odf, JSON.parse(message)); + } } -export const register = function(url, params, callback) { - _url = url; - _id = ('id' in params) ? params['id'] : _UUID(); - _on_signal = params['on_signal']; - _on_data = params['on_data']; - _i_chans = new ChannelPool(); - _o_chans = new ChannelPool(); +const on_disconnect = function () { + console.info('%s (%s) disconnected from %s.', ctx.name, ctx.app_id, ctx.url); + if (ctx.on_disconnect != null) { + ctx.on_disconnect(); + } +} - const on_failure = function(err) { - console.error('on_failure', err); - if (callback) - callback(false, err); - }; - - let payload = { - 'name': params['name'], - 'idf_list': params['idf_list'], - 'odf_list': params['odf_list'], - 'accept_protos': params['accept_protos'], - 'profile': params['profile'], - }; - - // filter out the empty `df_list`, in case of empty list, server reponsed 403. - ['idf_list', 'odf_list'].forEach( - x => { - if (Array.isArray(payload[x]) && payload[x].length == 0) - delete payload[x]; - } - ); - - superagent.put(_url + '/' + _id) - .type('json') - .accept('json') - .send(payload) - .end((err, res) => { - if(err) { - on_failure(err); - return; - } - - let metadata = res.body; - console.debug('register metadata', metadata); - if (typeof metadata === 'string') { - metadata = JSON.parse(metadata); - } - _rev = metadata['rev']; - _ctrl_i = metadata['ctrl_chans'][0]; - _ctrl_o = metadata['ctrl_chans'][1]; - _mqtt_host = metadata.url['host']; - _mqtt_port = metadata.url['ws_port']; - _mqtt_scheme = metadata.url['ws_scheme']; - - function on_connect() { - console.info('mqtt_connect'); - _i_chans.remove_all_df(); - _o_chans.remove_all_df(); - publish( - _ctrl_i, - JSON.stringify({'state': 'online', 'rev': _rev}), - true // retained message - ); - subscribe(_ctrl_o); - if (callback) { - callback({ - 'raproto': _url, - 'mqtt': metadata['url'], - 'id': _id, - 'd_name': metadata['name'], - }); +export const register = function (url, params, callback) { + ctx = new Context(); + + if (ctx.mqtt_client) { + console.error('Already registered'); + } + + ctx.url = url; + if (url == null || url == '') { + console.error('Invalid url: %s', ctx.url); + } + + ctx.app_id = params['id'] ? params['id'] : _UUID(); + + let body = { + 'name': params['name'], + 'idf_list': params['idf_list'], + 'odf_list': params['odf_list'], + 'accept_protos': params['accept_protos'] ? params['accept_protos'] : 'mqtt', + 'profile': params['profile'], + }; + + let _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; + if (typeof params['on_register'] != 'undefined' && typeof params['register_callback'] != 'undefined') { + console.error(_reg_msg); + } + else if (typeof params['on_register'] != 'undefined') { + ctx.on_register = params['on_register']; + } + else if (typeof params['register_callback'] != 'undefined') { + console.warning(_reg_msg); + ctx.on_register = params['register_callback']; + } + + // other callbacks + ctx.on_deregister = params['on_deregister']; + ctx.on_connect = params['on_connect']; + ctx.on_disconnect = params['on_disconnect']; + + const on_failure = function (err) { + console.error('on_failure', err); + if (callback) + callback(false, err); + }; + + // filter out the empty `df_list`, in case of empty list, server reponsed 403. + ['idf_list', 'odf_list'].forEach( + x => { + if (Array.isArray(body[x]) && body[x].length == 0) + delete body[x]; } - } - - _mqtt_client = mqtt.connect(_mqtt_scheme + '://' + _mqtt_host + ':' + _mqtt_port, { - clientId: 'mqttjs_' + _id, - will: { - topic: _ctrl_i, - // in most case of js DA, it never connect back - payload: JSON.stringify({'state': 'offline', 'rev': _rev}), - retain: true, - }, - keepalive: 30, // seems 60 is problematic for default mosquitto setup - }); - _mqtt_client.on('connect', on_connect); - _mqtt_client.on('reconnect', () => { console.info('mqtt_reconnect'); }); - _mqtt_client.on('error', (err) => { console.error('mqtt_error', err); }); - _mqtt_client.on('message', (topic, message, packet) => { - // Convert message from Uint8Array to String - on_message(topic, message.toString()); - }); + ); - }); + superagent.put(ctx.url + '/' + ctx.app_id) + .type('json') + .accept('json') + .send(body) + .end((err, res) => { + if (err) { + on_failure(err); + return; + } + + let metadata = res.body; + console.debug('register metadata', metadata); + if (typeof metadata === 'string') { + metadata = JSON.parse(metadata); + } + + ctx.name = metadata['name']; + ctx.mqtt_host = metadata['url']['host']; + ctx.mqtt_port = metadata['url']['ws_port']; + ctx.mqtt_username = metadata['username'] ? metadata['username'] : ''; + ctx.mqtt_password = metadata['password']? metadata['password'] : ''; + ctx.i_chans['ctrl'] = metadata['ctrl_chans'][0]; + ctx.o_chans['ctrl'] = metadata['ctrl_chans'][1]; + ctx.rev = metadata['rev']; + + ctx.mqtt_client = mqtt.connect(metadata.url['ws_scheme'] + '://' + ctx.mqtt_host + ':' + ctx.mqtt_port, { + clientId: 'iottalk-js-' + ctx.app_id, + username: ctx.mqtt_username, + password: ctx.mqtt_password, + will: { + topic: ctx.i_chans['ctrl'], + // in most case of js DA, it never connect back + payload: JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), + retain: true, + }, + keepalive: 30, // seems 60 is problematic for default mosquitto setup + }); + + ctx.mqtt_client.on('connect', (connack) => { + console.info('mqtt_connect'); + on_connect(); + if (callback) { + callback({ + 'raproto': ctx.url, + 'mqtt': metadata['url'], + 'id': ctx.app_id, + 'd_name': metadata['name'], + }); + } + }); + ctx.mqtt_client.on('reconnect', () => { + console.info('mqtt_reconnect'); + }); + ctx.mqtt_client.on('disconnect', (packet) => { + console.info('mqtt_disconnect'); + on_disconnect(); + }); + ctx.mqtt_client.on('error', (error) => { + console.error('mqtt_error', error); + }); + ctx.mqtt_client.on('message', (topic, message, packet) => { + // Convert message from Uint8Array to String + on_message(topic, message.toString()); + }); + + }); + + ctx.on_signal = params['on_signal']; + ctx.on_data = params['on_data']; + + if (ctx.on_register != null) { + ctx.on_register(); + } + console.log(ctx); } -export const deregister = function(callback) { - if (!_mqtt_client) { - if (callback) - return callback(true); - return; - } - - publish( - _ctrl_i, - JSON.stringify({'state': 'offline', 'rev': _rev}) - ); - _mqtt_client.end(); - superagent.del(_url +'/'+ _id) - .set('Content-Type', 'application/json') - .set('Accept', '*/*') - .send(JSON.stringify({'rev': _rev})) - .end((err, res) => { - if(err) { - console.error('deregister fail', err); +export const deregister = function (callback) { + if (!ctx.mqtt_client) { + console.error('Not registered'); if (callback) - return callback(false, err); - } - }); + return callback(true); + return; + } + + publish( + ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), + true + ); + ctx.mqtt_client.end(); + superagent.del(ctx.url + '/' + ctx.app_id) + .set('Content-Type', 'application/json') + .set('Accept', '*/*') + .send(JSON.stringify({ 'rev': ctx.rev })) + .end((err, res) => { + if (err) { + console.error('deregister fail', err); + if (callback) + return callback(false, err); + } + }); + ctx.mqtt_client = null; + + if (ctx.on_deregister != null) { + ctx.on_deregister(); + } - if (callback) - return callback(true); + if (callback) + return callback(true); } -export const push = function(idf_name, data, qos) { - if (!_mqtt_client || !_i_chans.topic(idf_name)) - return; - if(qos === undefined) - qos = 1; - publish(_i_chans.topic(idf_name), JSON.stringify(data), false, qos); +export const push = function (idf_name, data, qos) { + if (!ctx.mqtt_client) { + console.error('Not registered'); + return; + } + if (!ctx.i_chans.topic(idf_name)) { + return; + } + if (qos === undefined) + qos = 1; + + if (typeof data != 'object') { + data = [data]; + } + + publish(ctx.i_chans.topic(idf_name), JSON.stringify(data), false, qos); } -export const UUID = function() { - return _id ? _id : _UUID(); +export const UUID = function () { + return ctx.app_id ? ctx.app_id : _UUID(); } -export const connected = function() { - if( typeof _mqtt_client !== 'object' ) return false; - return _mqtt_client.connected; +export const connected = function () { + if (typeof ctx.mqtt_client !== 'object') return false; + return ctx.mqtt_client.connected; } -export const reconnecting = function() { - if( typeof _mqtt_client !== 'object' ) return false; - return _mqtt_client.reconnecting; +export const reconnecting = function () { + if (typeof ctx.mqtt_client !== 'object') return false; + return ctx.mqtt_client.reconnecting; } diff --git a/src/device-feature.js b/src/device-feature.js new file mode 100644 index 0000000..8704bb1 --- /dev/null +++ b/src/device-feature.js @@ -0,0 +1,16 @@ +export default class { + + constructor(params) { + this.df_name = params['df_name']; + this.df_type = params['df_type']; // idf | odf + this.param_type = params['param_type'] ? params['param_type'] : null; + + this.on_data = null + if (params['df_type'] == 'odf' && params['on_data']) + this.on_data = params['on_data']; + + this.push_data = null + if (params['df_type'] == 'idf' && params['push_data']) + this.push_data = params['push_data']; + } +} \ No newline at end of file From 12d7f354bc5b3131b00c3dd68f73723e94b5d2e8 Mon Sep 17 00:00:00 2001 From: Jason Chen Date: Thu, 25 Feb 2021 23:34:37 +0800 Subject: [PATCH 02/30] new dan2 and dai --- src/dai.js | 187 +++++++++++++++++++++++++++++++++++++--------- src/dan2.js | 77 ++++++++----------- webpack.config.js | 61 ++++++++++----- 3 files changed, 226 insertions(+), 99 deletions(-) diff --git a/src/dai.js b/src/dai.js index 71c55ad..5c144f6 100644 --- a/src/dai.js +++ b/src/dai.js @@ -1,44 +1,98 @@ import DeviceFeature from './device-feature.js' +let api_url; +let device_model; +let device_addr; +let device_name; +let persistent_binding; +let username; +let extra_setup_webpage; +let device_webpage; + +let register_callback; +let on_register; +let on_deregister; +let on_connect; +let on_disconnect; + +let push_interval; +let interval; + +let device_features = {}; +let flags = {}; + export const dai = function (profile, ida) { - var df_func = {}; - - let api_url = profile['api_url']; - let device_model = profile['device_model']; - let device_addr = profile['device_addr']; - let device_name = profile['device_name']; - let persistent_binding = profile['persistent_binding']; - let username = profile['username']; - let extra_setup_webpage = profile['extra_setup_webpage']; - let device_webpage = profile['device_webpage']; - - let register_callback = profile['register_callback']; - let on_register = profile['on_register']; - let on_deregister = profile['on_deregister']; - let on_connect = profile['on_connect']; - let on_disconnect = profile['on_disconnect']; - - let push_interval = profile['push_interval']; - let interval = profile['interval'] ? profile['interval'] : {}; - - let device_features = {}; - let flags = {}; - - for (let i = 0; i < profile.idf_list.length; i++) { - df_func[profile['idf_list'][i].name] = profile['idf_list'][i]; - profile['idf_list'][i] = profile['idf_list'][i].name; - } - for (let i = 0; i < profile.odf_list.length; i++) { - df_func[profile.odf_list[i].name] = profile.odf_list[i]; - profile.odf_list[i] = profile.odf_list[i].name; + api_url = profile['api_url']; + device_model = profile['device_model']; + device_addr = profile['device_addr']; + device_name = profile['device_name']; + persistent_binding = profile['persistent_binding'] ? profile['persistent_binding'] : false; + username = profile['username']; + extra_setup_webpage = profile['extra_setup_webpage'] ? profile['extra_setup_webpage'] : ''; + device_webpage = profile['device_webpage'] ? profile['device_webpage'] : ''; + + register_callback = profile['register_callback']; + on_register = profile['on_register']; + on_deregister = profile['on_deregister']; + on_connect = profile['on_connect']; + on_disconnect = profile['on_disconnect']; + + push_interval = profile['push_interval'] ? profile['push_interval'] : 1; + interval = profile['interval'] ? profile['interval'] : {}; + + parse_df_profile(profile, 'idf'); + parse_df_profile(profile, 'odf'); + + + function push_data(df_name) { + if (device_features[df_name].push_data == null) + return; + let _df_interval = interval[df_name] ? interval[df_name] : push_interval; + console.debug('%s : %s [message / %s ms]', df_name, flags[df_name], _df_interval); + let intervalID = setInterval( + (() => { + if (flags[df_name]) { + let _data = device_features[df_name].push_data(); + dan2.push(df_name, _data); + } + else { + clearInterval(intervalID) + } + }), _df_interval + ) } - function on_data(odf_name, data) { - df_func[odf_name](data); + function on_signal(signal, df_list) { + console.log('Receive signal : ', signal, df_list); + if ('CONNECT' == signal) { + df_list.forEach(df_name => { + if (!flags[df_name]) { + flags[df_name] = true; + push_data(df_name); + } + }); + } + else if ('DISCONNECT' == signal) { + df_list.forEach(df_name => { + flags[df_name] = false; + }); + } + else if ('SUSPEND' == signal) { + // Not use + } + else if ('RESUME' == signal) { + // Not use + } + return true; } - function on_signal(cmd, param) { - console.log('[cmd]', cmd, param); + function on_data(df_name, data) { + try { + device_features[df_name].on_data(data); + } catch (err) { + console.error(err); + return false; + } return true; } @@ -48,6 +102,19 @@ export const dai = function (profile, ida) { ida.ida_init(); } + if (!api_url) + throw 'api_url is required'; + + if (!device_model) + throw 'device_model not given.'; + + if (persistent_binding && !device_addr) + throw 'In case of `persistent_binding` set to `True`, ' + + 'the `device_addr` should be set and fixed.' + + if (Object.keys(device_features).length === 0) + throw 'Neither idf_list nor odf_list is empty.'; + let msg = { 'on_signal': on_signal, 'on_data': on_data, @@ -66,14 +133,62 @@ export const dai = function (profile, ida) { 'on_register': on_register, 'on_deregister': on_deregister, 'on_connect': on_connect, - // 'on_disconnect' : f + 'on_disconnect': () => { + df_list.forEach(df_name => { + flags[df_name] = false; + }); + console.debug('on_disconnect: _flag = %s', flags); + if (on_disconnect) { + return on_disconnect; + } + } } console.log(msg); dan2.register(api_url, msg, init_callback); + + window.onbeforeunload = function () { + try { + if (!persistent_binding) { + dan2.deregister(); + } + } catch (error) { + console.error('dai process cleanup exception: %s', error); + } + }; }; const parse_df_profile = function (profile, typ) { - let t = `${typ}_list`; + for (let i = 0; i < profile[`${typ}_list`].length; i++) { + let df_name; + let param_type; + let on_data; + let push_data; + if (typeof profile[`${typ}_list`][i] == 'function') { + df_name = profile[`${typ}_list`][i].name; + param_type = null; + on_data = push_data = profile[`${typ}_list`][i]; + profile[`${typ}_list`][i] = profile[`${typ}_list`][i].name; + } + else if (typeof profile[`${typ}_list`][i] == 'object' && profile[`${typ}_list`][i].length == 2) { + df_name = profile[`${typ}_list`][i][0].name; + param_type = profile[`${typ}_list`][i][1]; + on_data = push_data = profile[`${typ}_list`][i][0]; + profile[`${typ}_list`][i][0] = profile[`${typ}_list`][i][0].name; + } + else { + throw `Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`; + } + + let df = new DeviceFeature({ + 'df_name': df_name, + 'df_type': typ, + 'param_type': param_type, + 'push_data': push_data, + 'on_data': on_data + }); + + device_features[df_name] = df; + } } \ No newline at end of file diff --git a/src/dan2.js b/src/dan2.js index 1c22b12..622d0de 100644 --- a/src/dan2.js +++ b/src/dan2.js @@ -23,12 +23,12 @@ const publish = function (channel, message, retained, qos) { }); } -const subscribe = function (channel, qos) { +const subscribe = function (channel, callback, qos) { if (!ctx.mqtt_client) return; if (qos === undefined) qos = 2; - return ctx.mqtt_client.subscribe(channel, { qos: qos }); + return ctx.mqtt_client.subscribe(channel, { qos: qos }, callback); } const unsubscribe = function (channel) { @@ -42,7 +42,11 @@ const on_connect = function () { console.log('Successfully connect to %s', ctx.url); console.log('Device ID: %s.', ctx.app_id); console.log('Device name: %s.', ctx.name); - subscribe(ctx.o_chans['ctrl']); + subscribe(ctx.o_chans['ctrl'], (err, granted) => { + if (err) { + throw 'Subscribe to control channel failed'; + } + }); } else { console.info('Reconnect: %s.', ctx.name); @@ -51,19 +55,9 @@ const on_connect = function () { JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), true // retained message ); - // for (const k in ctx.o_chans) { - // if (typeof ctx.o_chans[k] != 'object') { - // subscribe(ctx.o_chans[k]); - // } - // else { - // for (const topic in ctx.o_chans[k]) { - // subscribe(topic); - // } - // } - // } } - // ctx.i_chans.remove_all_df(); - // ctx.o_chans.remove_all_df(); + ctx.i_chans.remove_all_df(); + ctx.o_chans.remove_all_df(); publish( ctx.i_chans['ctrl'], @@ -73,7 +67,7 @@ const on_connect = function () { _is_reconnect = true; - if (ctx.on_connect != null) { + if (ctx.on_connect) { ctx.on_connect(); } } @@ -132,7 +126,7 @@ const on_message = function (topic, message) { const on_disconnect = function () { console.info('%s (%s) disconnected from %s.', ctx.name, ctx.app_id, ctx.url); - if (ctx.on_disconnect != null) { + if (ctx.on_disconnect) { ctx.on_disconnect(); } } @@ -141,12 +135,12 @@ export const register = function (url, params, callback) { ctx = new Context(); if (ctx.mqtt_client) { - console.error('Already registered'); + throw 'Already registered'; } ctx.url = url; if (url == null || url == '') { - console.error('Invalid url: %s', ctx.url); + throw ('Invalid url: %s', ctx.url); } ctx.app_id = params['id'] ? params['id'] : _UUID(); @@ -161,7 +155,7 @@ export const register = function (url, params, callback) { let _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; if (typeof params['on_register'] != 'undefined' && typeof params['register_callback'] != 'undefined') { - console.error(_reg_msg); + throw _reg_msg; } else if (typeof params['on_register'] != 'undefined') { ctx.on_register = params['on_register']; @@ -176,11 +170,6 @@ export const register = function (url, params, callback) { ctx.on_connect = params['on_connect']; ctx.on_disconnect = params['on_disconnect']; - const on_failure = function (err) { - console.error('on_failure', err); - if (callback) - callback(false, err); - }; // filter out the empty `df_list`, in case of empty list, server reponsed 403. ['idf_list', 'odf_list'].forEach( @@ -196,7 +185,9 @@ export const register = function (url, params, callback) { .send(body) .end((err, res) => { if (err) { - on_failure(err); + console.error('on_failure', err); + if (callback) + callback(false, err); return; } @@ -210,7 +201,7 @@ export const register = function (url, params, callback) { ctx.mqtt_host = metadata['url']['host']; ctx.mqtt_port = metadata['url']['ws_port']; ctx.mqtt_username = metadata['username'] ? metadata['username'] : ''; - ctx.mqtt_password = metadata['password']? metadata['password'] : ''; + ctx.mqtt_password = metadata['password'] ? metadata['password'] : ''; ctx.i_chans['ctrl'] = metadata['ctrl_chans'][0]; ctx.o_chans['ctrl'] = metadata['ctrl_chans'][1]; ctx.rev = metadata['rev']; @@ -260,7 +251,7 @@ export const register = function (url, params, callback) { ctx.on_signal = params['on_signal']; ctx.on_data = params['on_data']; - if (ctx.on_register != null) { + if (ctx.on_register) { ctx.on_register(); } console.log(ctx); @@ -280,25 +271,23 @@ export const deregister = function (callback) { true ); ctx.mqtt_client.end(); + superagent.del(ctx.url + '/' + ctx.app_id) - .set('Content-Type', 'application/json') - .set('Accept', '*/*') + .type('json') + .accept('json') .send(JSON.stringify({ 'rev': ctx.rev })) - .end((err, res) => { - if (err) { - console.error('deregister fail', err); - if (callback) - return callback(false, err); + .then(res => { + ctx.mqtt_client = null; + if (ctx.on_deregister) { + ctx.on_deregister(); } + if (callback) + return callback(true); + }, err => { + console.error('deregister fail', err); + if (callback) + return callback(false); }); - ctx.mqtt_client = null; - - if (ctx.on_deregister != null) { - ctx.on_deregister(); - } - - if (callback) - return callback(true); } export const push = function (idf_name, data, qos) { @@ -315,7 +304,7 @@ export const push = function (idf_name, data, qos) { if (typeof data != 'object') { data = [data]; } - + publish(ctx.i_chans.topic(idf_name), JSON.stringify(data), false, qos); } diff --git a/webpack.config.js b/webpack.config.js index 92cdd01..6771535 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,23 +1,46 @@ const webpack = require('webpack'); -module.exports = { - target: 'web', - entry: __dirname + '/src/dan2.js', - output: { - path: __dirname + '/build-web', - filename: 'dan2-web.js', - library: ['dan2'], - libraryTarget: 'window', +module.exports = [ + { + target: 'web', + entry: __dirname + '/src/dan2.js', + output: { + path: __dirname + '/build-web', + filename: 'dan2-web.js', + library: ['dan2'], + libraryTarget: 'window', + }, + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + } + }, + ] + } }, - module: { - rules: [ - { - test: /\.m?js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - } - }, - ] + { + target: 'web', + entry: __dirname + '/src/dai.js', + output: { + path: __dirname + '/build-web', + filename: 'dai-web.js', + library: ['dai'], + libraryTarget: 'window', + }, + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + } + }, + ] + } } -} +] From 28e64608c4617bc179bb56c58af28cab883dbb38 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" <43653109+JasonChenGt@users.noreply.github.com> Date: Tue, 2 Mar 2021 15:56:37 +0800 Subject: [PATCH 03/30] Update context.js --- src/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context.js b/src/context.js index 43cbada..a0f9041 100644 --- a/src/context.js +++ b/src/context.js @@ -22,4 +22,4 @@ export default class { this.on_disconnect = null; // this._mqueue = queue.Queue() # storing the MQTTMessageInfo from ``publish`` } -} \ No newline at end of file +} From 9522f2ec9f91e636f9c63c124495f0141c870bd8 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 2 Mar 2021 16:14:30 +0800 Subject: [PATCH 04/30] add new line at end of file --- src/dai.js | 2 +- src/device-feature.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dai.js b/src/dai.js index 5c144f6..1dc943d 100644 --- a/src/dai.js +++ b/src/dai.js @@ -191,4 +191,4 @@ const parse_df_profile = function (profile, typ) { device_features[df_name] = df; } -} \ No newline at end of file +} diff --git a/src/device-feature.js b/src/device-feature.js index 8704bb1..bf3bb7a 100644 --- a/src/device-feature.js +++ b/src/device-feature.js @@ -13,4 +13,4 @@ export default class { if (params['df_type'] == 'idf' && params['push_data']) this.push_data = params['push_data']; } -} \ No newline at end of file +} From 1d2e18b9a331d697aca87442c90b4b1bbb4cf804 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Wed, 3 Mar 2021 13:32:44 +0800 Subject: [PATCH 05/30] change module name to iottalkjs --- src/context.js | 2 +- src/dai.js | 17 +++++++++-------- webpack.config.js | 48 +++++++++++++++++++++++------------------------ 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/context.js b/src/context.js index a0f9041..c32a9b7 100644 --- a/src/context.js +++ b/src/context.js @@ -1,4 +1,4 @@ -import ChannelPool from './channel-pool.js' +import ChannelPool from './channel-pool.js'; export default class { diff --git a/src/dai.js b/src/dai.js index 1dc943d..3efa2ae 100644 --- a/src/dai.js +++ b/src/dai.js @@ -1,4 +1,5 @@ -import DeviceFeature from './device-feature.js' +import DeviceFeature from './device-feature.js'; +import {push , register , deregister} from './dan2.js' let api_url; let device_model; @@ -53,13 +54,13 @@ export const dai = function (profile, ida) { (() => { if (flags[df_name]) { let _data = device_features[df_name].push_data(); - dan2.push(df_name, _data); + push(df_name, _data); } else { - clearInterval(intervalID) + clearInterval(intervalID); } }), _df_interval - ) + ); } function on_signal(signal, df_list) { @@ -110,7 +111,7 @@ export const dai = function (profile, ida) { if (persistent_binding && !device_addr) throw 'In case of `persistent_binding` set to `True`, ' + - 'the `device_addr` should be set and fixed.' + 'the `device_addr` should be set and fixed.'; if (Object.keys(device_features).length === 0) throw 'Neither idf_list nor odf_list is empty.'; @@ -142,16 +143,16 @@ export const dai = function (profile, ida) { return on_disconnect; } } - } + }; console.log(msg); - dan2.register(api_url, msg, init_callback); + register(api_url, msg, init_callback); window.onbeforeunload = function () { try { if (!persistent_binding) { - dan2.deregister(); + deregister(); } } catch (error) { console.error('dai process cleanup exception: %s', error); diff --git a/webpack.config.js b/webpack.config.js index 6771535..3332b03 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,34 +1,13 @@ const webpack = require('webpack'); module.exports = [ - { - target: 'web', - entry: __dirname + '/src/dan2.js', - output: { - path: __dirname + '/build-web', - filename: 'dan2-web.js', - library: ['dan2'], - libraryTarget: 'window', - }, - module: { - rules: [ - { - test: /\.m?js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - } - }, - ] - } - }, { target: 'web', entry: __dirname + '/src/dai.js', output: { path: __dirname + '/build-web', - filename: 'dai-web.js', - library: ['dai'], + filename: 'iottalkjs-web.js', + library: ['iottalkjs'], libraryTarget: 'window', }, module: { @@ -42,5 +21,26 @@ module.exports = [ }, ] } - } + }, + // { + // target: 'web', + // entry: __dirname + '/src/dan2.js', + // output: { + // path: __dirname + '/build-web', + // filename: 'dan2-web.js', + // library: ['dan2'], + // libraryTarget: 'window', + // }, + // module: { + // rules: [ + // { + // test: /\.m?js$/, + // exclude: /node_modules/, + // use: { + // loader: 'babel-loader', + // } + // }, + // ] + // } + // }, ] From f5bddc53697db3619d84ac70468a9c87a77cb08a Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 9 Mar 2021 00:10:16 +0800 Subject: [PATCH 06/30] dan: check whether the online message is published --- package.json | 4 +-- src/dai.js | 6 ++-- src/dan2.js | 86 ++++++++++++++++++++++++++--------------------- src/index.js | 3 ++ webpack.config.js | 28 +++------------ 5 files changed, 60 insertions(+), 67 deletions(-) create mode 100644 src/index.js diff --git a/package.json b/package.json index da1e160..ea063b2 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,14 @@ "name": "iottalk-js", "version": "2.0.4", "description": "IoTtalk javascript library SDK", - "main": "dan2.js", + "main": "iottalkjs.js", "directories": { "example": "examples" }, "scripts": { "start": "npx webpack -w --mode development", "build": "npm run build:node && npm run build:web", - "build:node": "npx babel src --out-dir build-node && npx ncc build build-node/dan2.js -o build-node/dist && mv build-node/dist/index.js build-node/dist/dan2.js", + "build:node": "npx babel src --out-dir build-node && npx ncc build build-node/index.js -o build-node/dist && mv build-node/dist/index.js build-node/dist/iottalkjs.js", "build:web": "npx webpack", "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/src/dai.js b/src/dai.js index 3efa2ae..48e29e1 100644 --- a/src/dai.js +++ b/src/dai.js @@ -50,14 +50,14 @@ export const dai = function (profile, ida) { return; let _df_interval = interval[df_name] ? interval[df_name] : push_interval; console.debug('%s : %s [message / %s ms]', df_name, flags[df_name], _df_interval); - let intervalID = setInterval( + let _push_interval = setInterval( (() => { if (flags[df_name]) { let _data = device_features[df_name].push_data(); push(df_name, _data); } else { - clearInterval(intervalID); + clearInterval(_push_interval); } }), _df_interval ); @@ -145,7 +145,7 @@ export const dai = function (profile, ida) { } }; - console.log(msg); + console.log('dai' , msg); register(api_url, msg, init_callback); diff --git a/src/dan2.js b/src/dan2.js index 622d0de..21f0d64 100644 --- a/src/dan2.js +++ b/src/dan2.js @@ -1,15 +1,15 @@ -import Context from './context.js' -import _UUID from './uuid.js' -import mqtt from 'mqtt' -import superagent from 'superagent' +import Context from './context.js'; +import _UUID from './uuid.js'; +import mqtt from 'mqtt'; +import superagent from 'superagent'; let ctx; +let _first_publish = false; let _is_reconnect = false; const publish = function (channel, message, retained, qos) { if (!ctx.mqtt_client) { - console.warn('unable to publish without ctx.mqtt_client'); - return; + throw 'unable to publish without ctx.mqtt_client'; } if (retained === undefined) retained = false; @@ -59,11 +59,15 @@ const on_connect = function () { ctx.i_chans.remove_all_df(); ctx.o_chans.remove_all_df(); - publish( + ctx.mqtt_client.publish( ctx.i_chans['ctrl'], JSON.stringify({ 'state': 'online', 'rev': ctx.rev }), - true // retained message - ); + { retain: true, qos: 2, }, + (err) => { + if (!err) { + _first_publish = true; + } + }); _is_reconnect = true; @@ -106,7 +110,7 @@ const on_message = function (topic, message) { } let res_message = { 'msg_id': signal['msg_id'], - } + }; if (typeof handling_result == 'boolean' && handling_result) { res_message['state'] = 'ok'; } else { @@ -179,20 +183,20 @@ export const register = function (url, params, callback) { } ); - superagent.put(ctx.url + '/' + ctx.app_id) - .type('json') - .accept('json') - .send(body) - .end((err, res) => { - if (err) { - console.error('on_failure', err); - if (callback) - callback(false, err); - return; - } - + new Promise( + (resolve, reject) => { + superagent.put(ctx.url + '/' + ctx.app_id) + .type('json') + .accept('json') + .send(body) + .then(res => { + resolve(res); + }, err => { + reject(err); + }); + }) + .then(res => { let metadata = res.body; - console.debug('register metadata', metadata); if (typeof metadata === 'string') { metadata = JSON.parse(metadata); } @@ -216,7 +220,7 @@ export const register = function (url, params, callback) { payload: JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), retain: true, }, - keepalive: 30, // seems 60 is problematic for default mosquitto setup + keepalive: 30, // seems 60 is problematic for default mosquitto setup }); ctx.mqtt_client.on('connect', (connack) => { @@ -224,10 +228,8 @@ export const register = function (url, params, callback) { on_connect(); if (callback) { callback({ - 'raproto': ctx.url, - 'mqtt': metadata['url'], - 'id': ctx.app_id, - 'd_name': metadata['name'], + 'metadata': metadata, + 'dan': ctx }); } }); @@ -242,19 +244,27 @@ export const register = function (url, params, callback) { console.error('mqtt_error', error); }); ctx.mqtt_client.on('message', (topic, message, packet) => { - // Convert message from Uint8Array to String - on_message(topic, message.toString()); + on_message(topic, message.toString()); // Convert message from Uint8Array to String }); - }); + ctx.on_signal = params['on_signal']; + ctx.on_data = params['on_data']; - ctx.on_signal = params['on_signal']; - ctx.on_data = params['on_data']; + setTimeout(() => { + if (!_first_publish) { + throw 'MQTT connection timeout'; + } + }, 5000); - if (ctx.on_register) { - ctx.on_register(); - } - console.log(ctx); + if (ctx.on_register) { + ctx.on_register(); + } + }, err => { + console.error('on_failure', err); + if (callback) + callback(false, err); + return; + }); } export const deregister = function (callback) { @@ -291,7 +301,7 @@ export const deregister = function (callback) { } export const push = function (idf_name, data, qos) { - if (!ctx.mqtt_client) { + if (!ctx.mqtt_client || !_first_publish) { console.error('Not registered'); return; } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..88f8bda --- /dev/null +++ b/src/index.js @@ -0,0 +1,3 @@ +import {dai} from './dai.js' +import * as dan from './dan2.js' +export { dai, dan }; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 3332b03..f31ce56 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,12 +2,13 @@ const webpack = require('webpack'); module.exports = [ { + mode: 'none', target: 'web', - entry: __dirname + '/src/dai.js', + entry: __dirname + '/src/index.js', output: { path: __dirname + '/build-web', filename: 'iottalkjs-web.js', - library: ['iottalkjs'], + library: 'iottalkjs', libraryTarget: 'window', }, module: { @@ -21,26 +22,5 @@ module.exports = [ }, ] } - }, - // { - // target: 'web', - // entry: __dirname + '/src/dan2.js', - // output: { - // path: __dirname + '/build-web', - // filename: 'dan2-web.js', - // library: ['dan2'], - // libraryTarget: 'window', - // }, - // module: { - // rules: [ - // { - // test: /\.m?js$/, - // exclude: /node_modules/, - // use: { - // loader: 'babel-loader', - // } - // }, - // ] - // } - // }, + } ] From 127de6378d3295b439551c9a6d4ee264af4b5b1c Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 9 Mar 2021 09:19:29 +0800 Subject: [PATCH 07/30] new Dummy Device example --- examples/Dummy_Device/index.html | 20 ++++++++++++++++++++ examples/Dummy_Device/js/ida.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 examples/Dummy_Device/index.html create mode 100644 examples/Dummy_Device/js/ida.js diff --git a/examples/Dummy_Device/index.html b/examples/Dummy_Device/index.html new file mode 100644 index 0000000..2f59301 --- /dev/null +++ b/examples/Dummy_Device/index.html @@ -0,0 +1,20 @@ + + + + + Dummy_Device + + + + + + + + Dummy_Sensor + _____________ + Dummy_Control + _____________ + + + \ No newline at end of file diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/ida.js new file mode 100644 index 0000000..585f02f --- /dev/null +++ b/examples/Dummy_Device/js/ida.js @@ -0,0 +1,32 @@ +$(function () { + function Dummy_Sensor() { + var number = Math.floor((1 + Math.random()) * 0x10000); + $('.IDF_value')[0].innerText = number; + return number; + } + + function Dummy_Control(data) { + $('.ODF_value')[0].innerText = data[0]; + } + + var profile = { + 'api_url': 'https://iottalk2.tw/csm', + 'device_model': 'Dummy_Device', + 'device_addr': 'c96ca71c-9e48-2a23-2868-acb420a2f105', + 'device_name': 'Dummy', + 'persistent_binding': true, + 'idf_list': [Dummy_Sensor], + 'odf_list': [Dummy_Control], + 'interval': { + 'Dummy_Sensor': 100, + } + }; + + /*******************************************************************/ + function ida_init() { + } + var ida = { + 'ida_init': ida_init, + }; + iottalkjs.dai(profile, ida); +}); From 67c85c1915dbae573796b19c532293f7f50a8c11 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" <43653109+JasonChenGt@users.noreply.github.com> Date: Tue, 9 Mar 2021 09:26:52 +0800 Subject: [PATCH 08/30] Update index.html --- examples/Dummy_Device/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Dummy_Device/index.html b/examples/Dummy_Device/index.html index 2f59301..f5f8f8a 100644 --- a/examples/Dummy_Device/index.html +++ b/examples/Dummy_Device/index.html @@ -17,4 +17,4 @@ _____________ - \ No newline at end of file + From e8107555da5817cd5681ce9fdf5ff3fd50208e3a Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" <43653109+JasonChenGt@users.noreply.github.com> Date: Tue, 9 Mar 2021 09:34:09 +0800 Subject: [PATCH 09/30] Update index.js --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 88f8bda..a368c07 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ import {dai} from './dai.js' import * as dan from './dan2.js' -export { dai, dan }; \ No newline at end of file +export { dai, dan }; From 6d190446984b47b0a87ed639c556e7809be7b0b3 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 9 Mar 2021 20:26:44 +0800 Subject: [PATCH 10/30] add exceptions --- src/dai.js | 29 +++++++++++++++-------------- src/dan2.js | 29 ++++++++++++++--------------- src/device-feature.js | 7 ++++++- src/exceptions.js | 10 ++++++++++ 4 files changed, 45 insertions(+), 30 deletions(-) create mode 100644 src/exceptions.js diff --git a/src/dai.js b/src/dai.js index 48e29e1..d5a9ef9 100644 --- a/src/dai.js +++ b/src/dai.js @@ -1,5 +1,6 @@ import DeviceFeature from './device-feature.js'; -import {push , register , deregister} from './dan2.js' +import { push, register, deregister } from './dan2.js' +import { RegistrationError, ArgumentError } from './exceptions.js' let api_url; let device_model; @@ -27,10 +28,10 @@ export const dai = function (profile, ida) { device_model = profile['device_model']; device_addr = profile['device_addr']; device_name = profile['device_name']; - persistent_binding = profile['persistent_binding'] ? profile['persistent_binding'] : false; + persistent_binding = profile['persistent_binding'] || false; username = profile['username']; - extra_setup_webpage = profile['extra_setup_webpage'] ? profile['extra_setup_webpage'] : ''; - device_webpage = profile['device_webpage'] ? profile['device_webpage'] : ''; + extra_setup_webpage = profile['extra_setup_webpage'] || ''; + device_webpage = profile['device_webpage'] || ''; register_callback = profile['register_callback']; on_register = profile['on_register']; @@ -38,8 +39,8 @@ export const dai = function (profile, ida) { on_connect = profile['on_connect']; on_disconnect = profile['on_disconnect']; - push_interval = profile['push_interval'] ? profile['push_interval'] : 1; - interval = profile['interval'] ? profile['interval'] : {}; + push_interval = profile['push_interval'] || 1; + interval = profile['interval'] || {}; parse_df_profile(profile, 'idf'); parse_df_profile(profile, 'odf'); @@ -48,7 +49,7 @@ export const dai = function (profile, ida) { function push_data(df_name) { if (device_features[df_name].push_data == null) return; - let _df_interval = interval[df_name] ? interval[df_name] : push_interval; + let _df_interval = interval[df_name] || push_interval; console.debug('%s : %s [message / %s ms]', df_name, flags[df_name], _df_interval); let _push_interval = setInterval( (() => { @@ -104,17 +105,17 @@ export const dai = function (profile, ida) { } if (!api_url) - throw 'api_url is required'; + throw new RegistrationError('api_url is required.'); if (!device_model) - throw 'device_model not given.'; + throw new RegistrationError('device_model not given.'); if (persistent_binding && !device_addr) - throw 'In case of `persistent_binding` set to `True`, ' + - 'the `device_addr` should be set and fixed.'; + throw new ArgumentError('In case of `persistent_binding` set to `True`, ' + + 'the `device_addr` should be set and fixed.'); if (Object.keys(device_features).length === 0) - throw 'Neither idf_list nor odf_list is empty.'; + throw new RegistrationError('Neither idf_list nor odf_list is empty.'); let msg = { 'on_signal': on_signal, @@ -145,7 +146,7 @@ export const dai = function (profile, ida) { } }; - console.log('dai' , msg); + console.log('dai', msg); register(api_url, msg, init_callback); @@ -179,7 +180,7 @@ const parse_df_profile = function (profile, typ) { profile[`${typ}_list`][i][0] = profile[`${typ}_list`][i][0].name; } else { - throw `Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`; + throw new RegistrationError(`Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`); } let df = new DeviceFeature({ diff --git a/src/dan2.js b/src/dan2.js index 21f0d64..7a4ffc4 100644 --- a/src/dan2.js +++ b/src/dan2.js @@ -2,6 +2,7 @@ import Context from './context.js'; import _UUID from './uuid.js'; import mqtt from 'mqtt'; import superagent from 'superagent'; +import { RegistrationError } from './exceptions.js' let ctx; let _first_publish = false; @@ -139,27 +140,27 @@ export const register = function (url, params, callback) { ctx = new Context(); if (ctx.mqtt_client) { - throw 'Already registered'; + throw new RegistrationError('Already registered'); } ctx.url = url; - if (url == null || url == '') { - throw ('Invalid url: %s', ctx.url); + if (!ctx.url || ctx.url == '') { + throw new RegistrationError(`Invalid url: ${ctx.url}`); } - ctx.app_id = params['id'] ? params['id'] : _UUID(); + ctx.app_id = params['id'] || _UUID(); let body = { 'name': params['name'], 'idf_list': params['idf_list'], 'odf_list': params['odf_list'], - 'accept_protos': params['accept_protos'] ? params['accept_protos'] : 'mqtt', + 'accept_protos': params['accept_protos'] || 'mqtt', 'profile': params['profile'], }; let _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; if (typeof params['on_register'] != 'undefined' && typeof params['register_callback'] != 'undefined') { - throw _reg_msg; + throw new RegistrationError(_reg_msg); } else if (typeof params['on_register'] != 'undefined') { ctx.on_register = params['on_register']; @@ -204,8 +205,8 @@ export const register = function (url, params, callback) { ctx.name = metadata['name']; ctx.mqtt_host = metadata['url']['host']; ctx.mqtt_port = metadata['url']['ws_port']; - ctx.mqtt_username = metadata['username'] ? metadata['username'] : ''; - ctx.mqtt_password = metadata['password'] ? metadata['password'] : ''; + ctx.mqtt_username = metadata['username'] || ''; + ctx.mqtt_password = metadata['password'] || ''; ctx.i_chans['ctrl'] = metadata['ctrl_chans'][0]; ctx.o_chans['ctrl'] = metadata['ctrl_chans'][1]; ctx.rev = metadata['rev']; @@ -252,7 +253,7 @@ export const register = function (url, params, callback) { setTimeout(() => { if (!_first_publish) { - throw 'MQTT connection timeout'; + throw new RegistrationError('MQTT connection timeout'); } }, 5000); @@ -269,10 +270,9 @@ export const register = function (url, params, callback) { export const deregister = function (callback) { if (!ctx.mqtt_client) { - console.error('Not registered'); if (callback) - return callback(true); - return; + callback(false); + throw new RegistrationError('Not registered'); } publish( @@ -302,8 +302,7 @@ export const deregister = function (callback) { export const push = function (idf_name, data, qos) { if (!ctx.mqtt_client || !_first_publish) { - console.error('Not registered'); - return; + throw new RegistrationError('Not registered'); } if (!ctx.i_chans.topic(idf_name)) { return; @@ -319,7 +318,7 @@ export const push = function (idf_name, data, qos) { } export const UUID = function () { - return ctx.app_id ? ctx.app_id : _UUID(); + return ctx.app_id || _UUID(); } export const connected = function () { diff --git a/src/device-feature.js b/src/device-feature.js index bf3bb7a..47dec3c 100644 --- a/src/device-feature.js +++ b/src/device-feature.js @@ -1,9 +1,14 @@ +import { ArgumentError } from './exceptions.js' export default class { constructor(params) { this.df_name = params['df_name']; + if (!this.df_name) { + throw new ArgumentError('device feature name is required.'); + } + this.df_type = params['df_type']; // idf | odf - this.param_type = params['param_type'] ? params['param_type'] : null; + this.param_type = params['param_type'] || [null]; this.on_data = null if (params['df_type'] == 'odf' && params['on_data']) diff --git a/src/exceptions.js b/src/exceptions.js new file mode 100644 index 0000000..923a5a4 --- /dev/null +++ b/src/exceptions.js @@ -0,0 +1,10 @@ +export class CustomError extends Error { + constructor(message) { + super(message); + this.name = this.constructor.name; + } +} + +export class ArgumentError extends CustomError { } + +export class RegistrationError extends CustomError { } From 19800bfde5e5d3fe1733a6b27181210444833f87 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Wed, 10 Mar 2021 23:43:40 +0800 Subject: [PATCH 11/30] rewrite dai --- examples/Dummy_Device/js/ida.js | 2 +- src/context.js | 1 - src/dai.js | 308 ++++++++++++++++---------------- src/dan2.js | 20 +-- src/device-feature.js | 6 +- src/exceptions.js | 2 +- src/index.js | 4 +- 7 files changed, 172 insertions(+), 171 deletions(-) diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/ida.js index 585f02f..898c756 100644 --- a/examples/Dummy_Device/js/ida.js +++ b/examples/Dummy_Device/js/ida.js @@ -28,5 +28,5 @@ $(function () { var ida = { 'ida_init': ida_init, }; - iottalkjs.dai(profile, ida); + new iottalkjs.dai(profile).run(ida); }); diff --git a/src/context.js b/src/context.js index c32a9b7..bb3d692 100644 --- a/src/context.js +++ b/src/context.js @@ -20,6 +20,5 @@ export default class { this.on_deregister = null; this.on_connect = null; this.on_disconnect = null; - // this._mqueue = queue.Queue() # storing the MQTTMessageInfo from ``publish`` } } diff --git a/src/dai.js b/src/dai.js index d5a9ef9..a4cd9b8 100644 --- a/src/dai.js +++ b/src/dai.js @@ -1,82 +1,66 @@ import DeviceFeature from './device-feature.js'; -import { push, register, deregister } from './dan2.js' -import { RegistrationError, ArgumentError } from './exceptions.js' - -let api_url; -let device_model; -let device_addr; -let device_name; -let persistent_binding; -let username; -let extra_setup_webpage; -let device_webpage; - -let register_callback; -let on_register; -let on_deregister; -let on_connect; -let on_disconnect; - -let push_interval; -let interval; - -let device_features = {}; -let flags = {}; - -export const dai = function (profile, ida) { - api_url = profile['api_url']; - device_model = profile['device_model']; - device_addr = profile['device_addr']; - device_name = profile['device_name']; - persistent_binding = profile['persistent_binding'] || false; - username = profile['username']; - extra_setup_webpage = profile['extra_setup_webpage'] || ''; - device_webpage = profile['device_webpage'] || ''; - - register_callback = profile['register_callback']; - on_register = profile['on_register']; - on_deregister = profile['on_deregister']; - on_connect = profile['on_connect']; - on_disconnect = profile['on_disconnect']; - - push_interval = profile['push_interval'] || 1; - interval = profile['interval'] || {}; - - parse_df_profile(profile, 'idf'); - parse_df_profile(profile, 'odf'); - - - function push_data(df_name) { - if (device_features[df_name].push_data == null) +import { push, register, deregister } from './dan2.js'; +import { RegistrationError, ArgumentError } from './exceptions.js'; + +export default class { + constructor(profile) { + this.api_url = profile['api_url']; + this.device_model = profile['device_model']; + this.device_addr = profile['device_addr']; + this.device_name = profile['device_name']; + this.persistent_binding = profile['persistent_binding'] || false; + this.username = profile['username']; + this.extra_setup_webpage = profile['extra_setup_webpage'] || ''; + this.device_webpage = profile['device_webpage'] || ''; + + this.register_callback = profile['register_callback']; + this.on_register = profile['on_register']; + this.on_deregister = profile['on_deregister']; + this.on_connect = profile['on_connect']; + this.on_disconnect = profile['on_disconnect']; + + this.push_interval = profile['push_interval'] || 1; + this.interval = profile['interval'] || {}; + + this.device_features = {}; + this.flags = {}; + + this.on_signal = this.on_signal.bind(this); + this.on_data = this.on_data.bind(this); + + this.parse_df_profile(profile, 'idf'); + this.parse_df_profile(profile, 'odf'); + } + + push_data(df_name) { + if (this.device_features[df_name].push_data == null) return; - let _df_interval = interval[df_name] || push_interval; - console.debug('%s : %s [message / %s ms]', df_name, flags[df_name], _df_interval); - let _push_interval = setInterval( - (() => { - if (flags[df_name]) { - let _data = device_features[df_name].push_data(); - push(df_name, _data); - } - else { - clearInterval(_push_interval); - } - }), _df_interval - ); + let _df_interval = this.interval[df_name] || this.push_interval; + console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} ms]`); + let _push_interval = setInterval(() => { + if (this.flags[df_name]) { + let _data = this.device_features[df_name].push_data(); + push(df_name, _data); + } + else { + clearInterval(_push_interval); + } + }, _df_interval); } - function on_signal(signal, df_list) { - console.log('Receive signal : ', signal, df_list); + on_signal(signal, df_list) { + console.log(`Receive signal : ${signal}, ${df_list}`); if ('CONNECT' == signal) { df_list.forEach(df_name => { - if (!flags[df_name]) { - flags[df_name] = true; - push_data(df_name); + if (!this.flags[df_name]) { + this.flags[df_name] = true; + this.push_data(df_name); } }); } else if ('DISCONNECT' == signal) { df_list.forEach(df_name => { - flags[df_name] = false; + this.flags[df_name] = false; }); } else if ('SUSPEND' == signal) { @@ -88,9 +72,9 @@ export const dai = function (profile, ida) { return true; } - function on_data(df_name, data) { + on_data(df_name, data) { try { - device_features[df_name].on_data(data); + this.device_features[df_name].on_data(data); } catch (err) { console.error(err); return false; @@ -98,99 +82,113 @@ export const dai = function (profile, ida) { return true; } - function init_callback(result) { - console.log('register:', result); - document.title = device_name; - ida.ida_init(); + _check_parameter() { + if (!this.api_url) + throw new RegistrationError('api_url is required.'); + + if (!this.device_model) + throw new RegistrationError('device_model not given.'); + + if (this.persistent_binding && !this.device_addr) + throw new ArgumentError('In case of `persistent_binding` set to `True`, ' + + 'the `device_addr` should be set and fixed.'); + + if (Object.keys(this.device_features).length === 0) + throw new RegistrationError('Neither idf_list nor odf_list is empty.'); } - if (!api_url) - throw new RegistrationError('api_url is required.'); - - if (!device_model) - throw new RegistrationError('device_model not given.'); - - if (persistent_binding && !device_addr) - throw new ArgumentError('In case of `persistent_binding` set to `True`, ' + - 'the `device_addr` should be set and fixed.'); - - if (Object.keys(device_features).length === 0) - throw new RegistrationError('Neither idf_list nor odf_list is empty.'); - - let msg = { - 'on_signal': on_signal, - 'on_data': on_data, - 'accept_protos': ['mqtt'], - 'id': device_addr, - 'idf_list': profile['idf_list'], - 'odf_list': profile['odf_list'], - 'name': device_name, - 'profile': { - 'model': device_model, - 'u_name': username, - 'extra_setup_webpage': extra_setup_webpage, - 'device_webpage': device_webpage, - }, - 'register_callback': register_callback, - 'on_register': on_register, - 'on_deregister': on_deregister, - 'on_connect': on_connect, - 'on_disconnect': () => { - df_list.forEach(df_name => { - flags[df_name] = false; - }); - console.debug('on_disconnect: _flag = %s', flags); - if (on_disconnect) { - return on_disconnect; - } - } - }; + run(ida) { + this._check_parameter(); - console.log('dai', msg); + let idf_list = []; + let odf_list = []; - register(api_url, msg, init_callback); + for (const [df_name, df] of Object.entries(this.device_features)) { + if (df.df_type == 'idf') + idf_list.push([df_name, df.df_type]); + else + odf_list.push([df_name, df.df_type]); + } - window.onbeforeunload = function () { - try { - if (!persistent_binding) { - deregister(); + let msg = { + 'on_signal': this.on_signal, + 'on_data': this.on_data, + 'accept_protos': ['mqtt'], + 'id': this.device_addr, + 'idf_list': idf_list, + 'odf_list': odf_list, + 'name': this.device_name, + 'profile': { + 'model': this.device_model, + 'u_name': this.username, + 'extra_setup_webpage': this.extra_setup_webpage, + 'device_webpage': this.device_webpage, + }, + 'register_callback': this.register_callback, + 'on_register': this.on_register, + 'on_deregister': this.on_deregister, + 'on_connect': this.on_connect, + 'on_disconnect': () => { + for (const key in this.flags) { + this.flags[key] = false; + } + console.debug(`on_disconnect: _flag = ${this.flags}`); + if (on_disconnect) { + return on_disconnect; + } } - } catch (error) { - console.error('dai process cleanup exception: %s', error); - } - }; -}; - -const parse_df_profile = function (profile, typ) { - for (let i = 0; i < profile[`${typ}_list`].length; i++) { - let df_name; - let param_type; - let on_data; - let push_data; - if (typeof profile[`${typ}_list`][i] == 'function') { - df_name = profile[`${typ}_list`][i].name; - param_type = null; - on_data = push_data = profile[`${typ}_list`][i]; - profile[`${typ}_list`][i] = profile[`${typ}_list`][i].name; - } - else if (typeof profile[`${typ}_list`][i] == 'object' && profile[`${typ}_list`][i].length == 2) { - df_name = profile[`${typ}_list`][i][0].name; - param_type = profile[`${typ}_list`][i][1]; - on_data = push_data = profile[`${typ}_list`][i][0]; - profile[`${typ}_list`][i][0] = profile[`${typ}_list`][i][0].name; - } - else { - throw new RegistrationError(`Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`); - } + }; - let df = new DeviceFeature({ - 'df_name': df_name, - 'df_type': typ, - 'param_type': param_type, - 'push_data': push_data, - 'on_data': on_data + console.log('dai', msg); + + register(this.api_url, msg, (result) => { + console.log('register', result); + document.title = this.device_name; + ida.ida_init(); }); - device_features[df_name] = df; + window.onbeforeunload = function () { + try { + if (!this.persistent_binding) { + deregister(); + } + } catch (error) { + console.error(`dai process cleanup exception: ${error}`); + } + }; + } + + parse_df_profile(profile, typ) { + for (let i = 0; i < profile[`${typ}_list`].length; i++) { + let df_name; + let param_type; + let on_data; + let push_data; + if (typeof profile[`${typ}_list`][i] == 'function') { + df_name = profile[`${typ}_list`][i].name; + param_type = null; + on_data = push_data = profile[`${typ}_list`][i]; + profile[`${typ}_list`][i] = profile[`${typ}_list`][i].name; + } + else if (typeof profile[`${typ}_list`][i] == 'object' && profile[`${typ}_list`][i].length == 2) { + df_name = profile[`${typ}_list`][i][0].name; + param_type = profile[`${typ}_list`][i][1]; + on_data = push_data = profile[`${typ}_list`][i][0]; + profile[`${typ}_list`][i][0] = profile[`${typ}_list`][i][0].name; + } + else { + throw new RegistrationError(`Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`); + } + + let df = new DeviceFeature({ + 'df_name': df_name, + 'df_type': typ, + 'param_type': param_type, + 'push_data': push_data, + 'on_data': on_data + }); + + this.device_features[df_name] = df; + } } } diff --git a/src/dan2.js b/src/dan2.js index 7a4ffc4..d285549 100644 --- a/src/dan2.js +++ b/src/dan2.js @@ -2,7 +2,7 @@ import Context from './context.js'; import _UUID from './uuid.js'; import mqtt from 'mqtt'; import superagent from 'superagent'; -import { RegistrationError } from './exceptions.js' +import { RegistrationError } from './exceptions.js'; let ctx; let _first_publish = false; @@ -40,9 +40,9 @@ const unsubscribe = function (channel) { const on_connect = function () { if (!_is_reconnect) { - console.log('Successfully connect to %s', ctx.url); - console.log('Device ID: %s.', ctx.app_id); - console.log('Device name: %s.', ctx.name); + console.log(`Successfully connect to ${ctx.url}`); + console.log(`Device ID: ${ctx.app_id}`); + console.log(`Device name: ${ctx.name}.`); subscribe(ctx.o_chans['ctrl'], (err, granted) => { if (err) { throw 'Subscribe to control channel failed'; @@ -50,7 +50,7 @@ const on_connect = function () { }); } else { - console.info('Reconnect: %s.', ctx.name); + console.info(`Reconnect: ${ctx.name}.`); publish( ctx.i_chans['ctrl'], JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), @@ -130,7 +130,7 @@ const on_message = function (topic, message) { } const on_disconnect = function () { - console.info('%s (%s) disconnected from %s.', ctx.name, ctx.app_id, ctx.url); + console.info(`${ctx.name} (${ctx.app_id}) disconnected from ${ctx.url}.`); if (ctx.on_disconnect) { ctx.on_disconnect(); } @@ -186,7 +186,7 @@ export const register = function (url, params, callback) { new Promise( (resolve, reject) => { - superagent.put(ctx.url + '/' + ctx.app_id) + superagent.put(`${ctx.url}/${ctx.app_id}`) .type('json') .accept('json') .send(body) @@ -211,8 +211,8 @@ export const register = function (url, params, callback) { ctx.o_chans['ctrl'] = metadata['ctrl_chans'][1]; ctx.rev = metadata['rev']; - ctx.mqtt_client = mqtt.connect(metadata.url['ws_scheme'] + '://' + ctx.mqtt_host + ':' + ctx.mqtt_port, { - clientId: 'iottalk-js-' + ctx.app_id, + ctx.mqtt_client = mqtt.connect(`${metadata.url['ws_scheme']}://${ctx.mqtt_host}:${ctx.mqtt_port}`, { + clientId: `iottalk-js-${ctx.app_id}`, username: ctx.mqtt_username, password: ctx.mqtt_password, will: { @@ -282,7 +282,7 @@ export const deregister = function (callback) { ); ctx.mqtt_client.end(); - superagent.del(ctx.url + '/' + ctx.app_id) + superagent.del(`${ctx.url}/${ctx.app_id}`) .type('json') .accept('json') .send(JSON.stringify({ 'rev': ctx.rev })) diff --git a/src/device-feature.js b/src/device-feature.js index 47dec3c..48314c5 100644 --- a/src/device-feature.js +++ b/src/device-feature.js @@ -1,4 +1,4 @@ -import { ArgumentError } from './exceptions.js' +import { ArgumentError } from './exceptions.js'; export default class { constructor(params) { @@ -8,6 +8,10 @@ export default class { } this.df_type = params['df_type']; // idf | odf + if (this.df_type != 'idf' && this.df_type != 'odf') { + throw new ArgumentError(`${this.df_name} df_type must be "idf" or "odf"`); + } + this.param_type = params['param_type'] || [null]; this.on_data = null diff --git a/src/exceptions.js b/src/exceptions.js index 923a5a4..680e312 100644 --- a/src/exceptions.js +++ b/src/exceptions.js @@ -1,4 +1,4 @@ -export class CustomError extends Error { +class CustomError extends Error { constructor(message) { super(message); this.name = this.constructor.name; diff --git a/src/index.js b/src/index.js index a368c07..910cd85 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -import {dai} from './dai.js' -import * as dan from './dan2.js' +import dai from './dai.js'; +import * as dan from './dan2.js'; export { dai, dan }; From ac1a6e9ed51882e9a3545a637da3a5b8fe909a15 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 16 Mar 2021 17:02:21 +0800 Subject: [PATCH 12/30] rename dan.js --- .travis.yml | 6 +++--- src/dai.js | 11 +++++++---- src/{dan2.js => dan.js} | 2 +- src/index.js | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) rename src/{dan2.js => dan.js} (99%) diff --git a/.travis.yml b/.travis.yml index e1481ee..7ee2f12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,13 +19,13 @@ before_deploy: - export DISTDIR=/tmp/dist - git fetch origin gh-pages:gh-pages - git worktree add ${DISTDIR} gh-pages - - cp ./build-web/dan2-web.js ${DISTDIR}/dan2.js - - cp ./build-web/dan2-web.js ${DISTDIR}/dan2-${TRAVIS_COMMIT}.js + - cp ./build-web/iottalkjs-web.js ${DISTDIR}/iottalkjs.js + - cp ./build-web/iottalkjs-web.js ${DISTDIR}/iottalkjs-${TRAVIS_COMMIT}.js - echo "TRAVIS_TAG = ${TRAVIS_TAG}" - echo "TRAVIS_BRANCH = ${TRAVIS_BRANCH}" - if [ "${TRAVIS_TAG}" ]; then echo 'Add tags build'; - cp ./build-web/dan2-web.js ${DISTDIR}/dan2-${TRAVIS_TAG}.js; + cp ./build-web/iottalkjs-web.js ${DISTDIR}/iottalkjs-${TRAVIS_TAG}.js; fi deploy: diff --git a/src/dai.js b/src/dai.js index a4cd9b8..0a07355 100644 --- a/src/dai.js +++ b/src/dai.js @@ -1,5 +1,5 @@ import DeviceFeature from './device-feature.js'; -import { push, register, deregister } from './dan2.js'; +import { push, register, deregister } from './dan.js'; import { RegistrationError, ArgumentError } from './exceptions.js'; export default class { @@ -19,7 +19,7 @@ export default class { this.on_connect = profile['on_connect']; this.on_disconnect = profile['on_disconnect']; - this.push_interval = profile['push_interval'] || 1; + this.push_interval = profile['push_interval']; this.interval = profile['interval'] || {}; this.device_features = {}; @@ -35,6 +35,9 @@ export default class { push_data(df_name) { if (this.device_features[df_name].push_data == null) return; + if (typeof this.push_interval === 'undefined') { + this.push_interval = 1; + } let _df_interval = this.interval[df_name] || this.push_interval; console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} ms]`); let _push_interval = setInterval(() => { @@ -164,13 +167,13 @@ export default class { let param_type; let on_data; let push_data; - if (typeof profile[`${typ}_list`][i] == 'function') { + if (typeof profile[`${typ}_list`][i] === 'function') { df_name = profile[`${typ}_list`][i].name; param_type = null; on_data = push_data = profile[`${typ}_list`][i]; profile[`${typ}_list`][i] = profile[`${typ}_list`][i].name; } - else if (typeof profile[`${typ}_list`][i] == 'object' && profile[`${typ}_list`][i].length == 2) { + else if (typeof profile[`${typ}_list`][i] === 'object' && profile[`${typ}_list`][i].length == 2) { df_name = profile[`${typ}_list`][i][0].name; param_type = profile[`${typ}_list`][i][1]; on_data = push_data = profile[`${typ}_list`][i][0]; diff --git a/src/dan2.js b/src/dan.js similarity index 99% rename from src/dan2.js rename to src/dan.js index d285549..2866f3a 100644 --- a/src/dan2.js +++ b/src/dan.js @@ -112,7 +112,7 @@ const on_message = function (topic, message) { let res_message = { 'msg_id': signal['msg_id'], }; - if (typeof handling_result == 'boolean' && handling_result) { + if (typeof handling_result === 'boolean' && handling_result) { res_message['state'] = 'ok'; } else { res_message['state'] = 'error'; diff --git a/src/index.js b/src/index.js index 910cd85..c7dd058 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ import dai from './dai.js'; -import * as dan from './dan2.js'; +import * as dan from './dan.js'; export { dai, dan }; From d0a1eba90d4e5ad4150ef91e28fab3e8ce338146 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 16 Mar 2021 20:20:09 +0800 Subject: [PATCH 13/30] change the interval unit to seconds --- examples/Dummy_Device/js/ida.js | 12 ++--- src/dai.js | 83 ++++++++++++++++----------------- 2 files changed, 43 insertions(+), 52 deletions(-) diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/ida.js index 898c756..0d1330d 100644 --- a/examples/Dummy_Device/js/ida.js +++ b/examples/Dummy_Device/js/ida.js @@ -2,14 +2,14 @@ $(function () { function Dummy_Sensor() { var number = Math.floor((1 + Math.random()) * 0x10000); $('.IDF_value')[0].innerText = number; - return number; + return [number]; } function Dummy_Control(data) { $('.ODF_value')[0].innerText = data[0]; } - var profile = { + var option = { 'api_url': 'https://iottalk2.tw/csm', 'device_model': 'Dummy_Device', 'device_addr': 'c96ca71c-9e48-2a23-2868-acb420a2f105', @@ -22,11 +22,5 @@ $(function () { } }; - /*******************************************************************/ - function ida_init() { - } - var ida = { - 'ida_init': ida_init, - }; - new iottalkjs.dai(profile).run(ida); + new iottalkjs.dai(option).run(); }); diff --git a/src/dai.js b/src/dai.js index 0a07355..0f13be7 100644 --- a/src/dai.js +++ b/src/dai.js @@ -3,24 +3,24 @@ import { push, register, deregister } from './dan.js'; import { RegistrationError, ArgumentError } from './exceptions.js'; export default class { - constructor(profile) { - this.api_url = profile['api_url']; - this.device_model = profile['device_model']; - this.device_addr = profile['device_addr']; - this.device_name = profile['device_name']; - this.persistent_binding = profile['persistent_binding'] || false; - this.username = profile['username']; - this.extra_setup_webpage = profile['extra_setup_webpage'] || ''; - this.device_webpage = profile['device_webpage'] || ''; - - this.register_callback = profile['register_callback']; - this.on_register = profile['on_register']; - this.on_deregister = profile['on_deregister']; - this.on_connect = profile['on_connect']; - this.on_disconnect = profile['on_disconnect']; - - this.push_interval = profile['push_interval']; - this.interval = profile['interval'] || {}; + constructor(option) { + this.api_url = option['api_url']; + this.device_model = option['device_model']; + this.device_addr = option['device_addr']; + this.device_name = option['device_name']; + this.persistent_binding = option['persistent_binding'] || false; + this.username = option['username']; + this.extra_setup_webpage = option['extra_setup_webpage'] || ''; + this.device_webpage = option['device_webpage'] || ''; + + this.register_callback = option['register_callback']; + this.on_register = option['on_register']; + this.on_deregister = option['on_deregister']; + this.on_connect = option['on_connect']; + this.on_disconnect = option['on_disconnect']; + + this.push_interval = typeof option['push_interval'] != 'undefined' ? option['push_interval'] : 1; + this.interval = option['interval'] || {}; this.device_features = {}; this.flags = {}; @@ -28,31 +28,29 @@ export default class { this.on_signal = this.on_signal.bind(this); this.on_data = this.on_data.bind(this); - this.parse_df_profile(profile, 'idf'); - this.parse_df_profile(profile, 'odf'); + this.parse_df_profile(option, 'idf'); + this.parse_df_profile(option, 'odf'); } push_data(df_name) { if (this.device_features[df_name].push_data == null) return; - if (typeof this.push_interval === 'undefined') { - this.push_interval = 1; - } let _df_interval = this.interval[df_name] || this.push_interval; - console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} ms]`); + console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} s]`); let _push_interval = setInterval(() => { - if (this.flags[df_name]) { - let _data = this.device_features[df_name].push_data(); - push(df_name, _data); + let _data = this.device_features[df_name].push_data(); + console.log(_data); + if (!this.flags[df_name] && typeof _data != 'undefined') { + clearInterval(_push_interval); } else { - clearInterval(_push_interval); + push(df_name, _data); } - }, _df_interval); + }, _df_interval * 1000); } on_signal(signal, df_list) { - console.log(`Receive signal : ${signal}, ${df_list}`); + console.log(`Receive signal: ${signal}, ${df_list}`); if ('CONNECT' == signal) { df_list.forEach(df_name => { if (!this.flags[df_name]) { @@ -100,7 +98,7 @@ export default class { throw new RegistrationError('Neither idf_list nor odf_list is empty.'); } - run(ida) { + run() { this._check_parameter(); let idf_list = []; @@ -147,7 +145,6 @@ export default class { register(this.api_url, msg, (result) => { console.log('register', result); document.title = this.device_name; - ida.ida_init(); }); window.onbeforeunload = function () { @@ -161,23 +158,23 @@ export default class { }; } - parse_df_profile(profile, typ) { - for (let i = 0; i < profile[`${typ}_list`].length; i++) { + parse_df_profile(option, typ) { + for (let i = 0; i < option[`${typ}_list`].length; i++) { let df_name; let param_type; let on_data; let push_data; - if (typeof profile[`${typ}_list`][i] === 'function') { - df_name = profile[`${typ}_list`][i].name; + if (typeof option[`${typ}_list`][i] === 'function') { + df_name = option[`${typ}_list`][i].name; param_type = null; - on_data = push_data = profile[`${typ}_list`][i]; - profile[`${typ}_list`][i] = profile[`${typ}_list`][i].name; + on_data = push_data = option[`${typ}_list`][i]; + option[`${typ}_list`][i] = option[`${typ}_list`][i].name; } - else if (typeof profile[`${typ}_list`][i] === 'object' && profile[`${typ}_list`][i].length == 2) { - df_name = profile[`${typ}_list`][i][0].name; - param_type = profile[`${typ}_list`][i][1]; - on_data = push_data = profile[`${typ}_list`][i][0]; - profile[`${typ}_list`][i][0] = profile[`${typ}_list`][i][0].name; + else if (typeof option[`${typ}_list`][i] === 'object' && option[`${typ}_list`][i].length == 2) { + df_name = option[`${typ}_list`][i][0].name; + param_type = option[`${typ}_list`][i][1]; + on_data = push_data = option[`${typ}_list`][i][0]; + option[`${typ}_list`][i][0] = option[`${typ}_list`][i][0].name; } else { throw new RegistrationError(`Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`); From fb1ca008d3ecb770c764cddd084d1c059e5b6059 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Wed, 17 Mar 2021 16:41:34 +0800 Subject: [PATCH 14/30] change coding style --- examples/Dummy_Device/js/ida.js | 3 ++- src/dai.js | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/ida.js index 0d1330d..36bf52d 100644 --- a/examples/Dummy_Device/js/ida.js +++ b/examples/Dummy_Device/js/ida.js @@ -17,8 +17,9 @@ $(function () { 'persistent_binding': true, 'idf_list': [Dummy_Sensor], 'odf_list': [Dummy_Control], + 'push_interval': 0, 'interval': { - 'Dummy_Sensor': 100, + 'Dummy_Sensor': 1.5, } }; diff --git a/src/dai.js b/src/dai.js index 0f13be7..b7ddef9 100644 --- a/src/dai.js +++ b/src/dai.js @@ -35,15 +35,15 @@ export default class { push_data(df_name) { if (this.device_features[df_name].push_data == null) return; - let _df_interval = this.interval[df_name] || this.push_interval; + let _df_interval = typeof this.interval[df_name] != 'undefined' ? this.interval[df_name] : this.push_interval; console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} s]`); let _push_interval = setInterval(() => { let _data = this.device_features[df_name].push_data(); - console.log(_data); - if (!this.flags[df_name] && typeof _data != 'undefined') { + if (!this.flags[df_name]) { clearInterval(_push_interval); + return; } - else { + if (typeof _data != 'undefined') { push(df_name, _data); } }, _df_interval * 1000); @@ -53,10 +53,11 @@ export default class { console.log(`Receive signal: ${signal}, ${df_list}`); if ('CONNECT' == signal) { df_list.forEach(df_name => { - if (!this.flags[df_name]) { - this.flags[df_name] = true; - this.push_data(df_name); + if (this.flags[df_name]) { + return; } + this.flags[df_name] = true; + this.push_data(df_name); }); } else if ('DISCONNECT' == signal) { From 9e8e91a6be0c1dc7a4e3c97be89d17196453008b Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Thu, 18 Mar 2021 00:33:04 +0800 Subject: [PATCH 15/30] rewrite dan --- examples/Dummy_Device/js/ida.js | 5 +- src/dai.js | 42 ++- src/dan.js | 557 ++++++++++++++++---------------- 3 files changed, 312 insertions(+), 292 deletions(-) diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/ida.js index 36bf52d..5437d50 100644 --- a/examples/Dummy_Device/js/ida.js +++ b/examples/Dummy_Device/js/ida.js @@ -15,8 +15,9 @@ $(function () { 'device_addr': 'c96ca71c-9e48-2a23-2868-acb420a2f105', 'device_name': 'Dummy', 'persistent_binding': true, - 'idf_list': [Dummy_Sensor], - 'odf_list': [Dummy_Control], + 'idf_list': [['Dummy_Sensor', ['int']]], + 'odf_list': ['Dummy_Control'], + 'df_function_list': [Dummy_Sensor, Dummy_Control], 'push_interval': 0, 'interval': { 'Dummy_Sensor': 1.5, diff --git a/src/dai.js b/src/dai.js index b7ddef9..3f44ecb 100644 --- a/src/dai.js +++ b/src/dai.js @@ -1,8 +1,9 @@ import DeviceFeature from './device-feature.js'; -import { push, register, deregister } from './dan.js'; +import { Client } from './dan.js'; import { RegistrationError, ArgumentError } from './exceptions.js'; export default class { + constructor(option) { this.api_url = option['api_url']; this.device_model = option['device_model']; @@ -19,7 +20,7 @@ export default class { this.on_connect = option['on_connect']; this.on_disconnect = option['on_disconnect']; - this.push_interval = typeof option['push_interval'] != 'undefined' ? option['push_interval'] : 1; + this.push_interval = option['push_interval'] != undefined ? option['push_interval'] : 1; this.interval = option['interval'] || {}; this.device_features = {}; @@ -35,7 +36,7 @@ export default class { push_data(df_name) { if (this.device_features[df_name].push_data == null) return; - let _df_interval = typeof this.interval[df_name] != 'undefined' ? this.interval[df_name] : this.push_interval; + let _df_interval = this.interval[df_name] != undefined ? this.interval[df_name] : this.push_interval; console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} s]`); let _push_interval = setInterval(() => { let _data = this.device_features[df_name].push_data(); @@ -43,9 +44,11 @@ export default class { clearInterval(_push_interval); return; } - if (typeof _data != 'undefined') { - push(df_name, _data); + if (_data === undefined) { + return; } + this.dan.push(df_name, _data); + }, _df_interval * 1000); } @@ -84,6 +87,13 @@ export default class { return true; } + df_func_name(df_name) { + if (df_name.match(/-[A-Z]?(I|O)[0-9]?$/i)) { + return df_name.replace('-', '_'); + } + return df_name; + } + _check_parameter() { if (!this.api_url) throw new RegistrationError('api_url is required.'); @@ -102,6 +112,8 @@ export default class { run() { this._check_parameter(); + this.dan = new Client(); + let idf_list = []; let odf_list = []; @@ -143,7 +155,7 @@ export default class { console.log('dai', msg); - register(this.api_url, msg, (result) => { + this.dan.register(this.api_url, msg, (result) => { console.log('register', result); document.title = this.device_name; }); @@ -151,7 +163,7 @@ export default class { window.onbeforeunload = function () { try { if (!this.persistent_binding) { - deregister(); + this.dan.deregister(); } } catch (error) { console.error(`dai process cleanup exception: ${error}`); @@ -165,22 +177,24 @@ export default class { let param_type; let on_data; let push_data; - if (typeof option[`${typ}_list`][i] === 'function') { - df_name = option[`${typ}_list`][i].name; + if (typeof option[`${typ}_list`][i] === 'string') { + df_name = option[`${typ}_list`][i]; param_type = null; - on_data = push_data = option[`${typ}_list`][i]; - option[`${typ}_list`][i] = option[`${typ}_list`][i].name; } else if (typeof option[`${typ}_list`][i] === 'object' && option[`${typ}_list`][i].length == 2) { - df_name = option[`${typ}_list`][i][0].name; + df_name = option[`${typ}_list`][i][0]; param_type = option[`${typ}_list`][i][1]; - on_data = push_data = option[`${typ}_list`][i][0]; - option[`${typ}_list`][i][0] = option[`${typ}_list`][i][0].name; } else { throw new RegistrationError(`Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`); } + option['df_function_list'].forEach(df_function => { + if (this.df_func_name(df_name) == df_function.name) { + on_data = push_data = df_function; + } + }); + let df = new DeviceFeature({ 'df_name': df_name, 'df_type': typ, diff --git a/src/dan.js b/src/dan.js index 2866f3a..a26ea39 100644 --- a/src/dan.js +++ b/src/dan.js @@ -4,329 +4,334 @@ import mqtt from 'mqtt'; import superagent from 'superagent'; import { RegistrationError } from './exceptions.js'; -let ctx; -let _first_publish = false; -let _is_reconnect = false; +export class Client { -const publish = function (channel, message, retained, qos) { - if (!ctx.mqtt_client) { - throw 'unable to publish without ctx.mqtt_client'; + constructor() { + this.ctx = new Context(); + this._first_publish = false; + this._is_reconnect = false; } - if (retained === undefined) - retained = false; - if (qos === undefined) - qos = 2; - - ctx.mqtt_client.publish(channel, message, { - retain: retained, - qos: qos, - }); -} + publish(channel, message, retained, qos) { + if (!this.ctx.mqtt_client) { + throw 'unable to publish without ctx.mqtt_client'; + } -const subscribe = function (channel, callback, qos) { - if (!ctx.mqtt_client) - return; - if (qos === undefined) - qos = 2; - return ctx.mqtt_client.subscribe(channel, { qos: qos }, callback); -} + if (retained === undefined) + retained = false; -const unsubscribe = function (channel) { - if (!ctx.mqtt_client) - return; - return ctx.mqtt_client.unsubscribe(channel); -} + if (qos === undefined) + qos = 2; -const on_connect = function () { - if (!_is_reconnect) { - console.log(`Successfully connect to ${ctx.url}`); - console.log(`Device ID: ${ctx.app_id}`); - console.log(`Device name: ${ctx.name}.`); - subscribe(ctx.o_chans['ctrl'], (err, granted) => { - if (err) { - throw 'Subscribe to control channel failed'; - } + this.ctx.mqtt_client.publish(channel, message, { + retain: retained, + qos: qos, }); } - else { - console.info(`Reconnect: ${ctx.name}.`); - publish( - ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), - true // retained message - ); + + subscribe(channel, callback, qos) { + if (!this.ctx.mqtt_client) + return; + + if (qos === undefined) + qos = 2; + + return this.ctx.mqtt_client.subscribe(channel, { qos: qos }, callback); } - ctx.i_chans.remove_all_df(); - ctx.o_chans.remove_all_df(); - - ctx.mqtt_client.publish( - ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'online', 'rev': ctx.rev }), - { retain: true, qos: 2, }, - (err) => { - if (!err) { - _first_publish = true; - } - }); - _is_reconnect = true; + unsubscribe(channel) { + if (!this.ctx.mqtt_client) + return; - if (ctx.on_connect) { - ctx.on_connect(); + return this.ctx.mqtt_client.unsubscribe(channel); } -} -const on_message = function (topic, message) { - if (topic == ctx.o_chans['ctrl']) { - let signal = JSON.parse(message); - let handling_result = null; - switch (signal['command']) { - case 'CONNECT': - if ('idf' in signal) { - let idf = signal['idf']; - ctx.i_chans.add(idf, signal['topic']); - handling_result = ctx.on_signal(signal['command'], [idf]); - - } else if ('odf' in signal) { - let odf = signal['odf']; - ctx.o_chans.add(odf, signal['topic']); - handling_result = ctx.on_signal(signal['command'], [odf]); - subscribe(ctx.o_chans.topic(odf)); - } - break; - case 'DISCONNECT': - if ('idf' in signal) { - let idf = signal['idf']; - ctx.i_chans.remove_df(idf); - handling_result = ctx.on_signal(signal['command'], [idf]); - - } else if ('odf' in signal) { - let odf = signal['odf']; - unsubscribe(ctx.o_chans.topic(odf)); - ctx.o_chans.remove_df(odf); - handling_result = ctx.on_signal(signal['command'], [odf]); + on_connect() { + if (!this._is_reconnect) { + console.log(`Successfully connect to ${this.ctx.url}`); + console.log(`Device ID: ${this.ctx.app_id}`); + console.log(`Device name: ${this.ctx.name}.`); + this.subscribe(this.ctx.o_chans['ctrl'], (err, granted) => { + if (err) { + throw 'Subscribe to control channel failed'; } - break; + }); } - let res_message = { - 'msg_id': signal['msg_id'], - }; - if (typeof handling_result === 'boolean' && handling_result) { - res_message['state'] = 'ok'; - } else { - res_message['state'] = 'error'; - res_message['reason'] = handling_result[1]; + else { + console.info(`Reconnect: ${this.ctx.name}.`); + this.publish( + this.ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'offline', 'rev': this.ctx.rev }), + true // retained message + ); } - publish(ctx.i_chans['ctrl'], JSON.stringify(res_message)); - return; - } - else { - let odf = ctx.o_chans.df(topic); - if (!odf) - return; - ctx.on_data(odf, JSON.parse(message)); - } -} - -const on_disconnect = function () { - console.info(`${ctx.name} (${ctx.app_id}) disconnected from ${ctx.url}.`); - if (ctx.on_disconnect) { - ctx.on_disconnect(); - } -} + this.ctx.i_chans.remove_all_df(); + this.ctx.o_chans.remove_all_df(); + + this.ctx.mqtt_client.publish( + this.ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'online', 'rev': this.ctx.rev }), + { retain: true, qos: 2, }, + (err) => { + if (!err) { + this._first_publish = true; + } + }); -export const register = function (url, params, callback) { - ctx = new Context(); + this._is_reconnect = true; - if (ctx.mqtt_client) { - throw new RegistrationError('Already registered'); + if (this.ctx.on_connect) { + this.ctx.on_connect(); + } } - ctx.url = url; - if (!ctx.url || ctx.url == '') { - throw new RegistrationError(`Invalid url: ${ctx.url}`); + on_message(topic, message) { + if (topic == this.ctx.o_chans['ctrl']) { + let signal = JSON.parse(message); + let handling_result = null; + switch (signal['command']) { + case 'CONNECT': + if ('idf' in signal) { + let idf = signal['idf']; + this.ctx.i_chans.add(idf, signal['topic']); + handling_result = this.ctx.on_signal(signal['command'], [idf]); + + } else if ('odf' in signal) { + let odf = signal['odf']; + this.ctx.o_chans.add(odf, signal['topic']); + handling_result = this.ctx.on_signal(signal['command'], [odf]); + this.subscribe(this.ctx.o_chans.topic(odf)); + } + break; + case 'DISCONNECT': + if ('idf' in signal) { + let idf = signal['idf']; + this.ctx.i_chans.remove_df(idf); + handling_result = this.ctx.on_signal(signal['command'], [idf]); + + } else if ('odf' in signal) { + let odf = signal['odf']; + this.unsubscribe(this.ctx.o_chans.topic(odf)); + this.ctx.o_chans.remove_df(odf); + handling_result = this.ctx.on_signal(signal['command'], [odf]); + } + break; + } + let res_message = { + 'msg_id': signal['msg_id'], + }; + if (typeof handling_result === 'boolean' && handling_result) { + res_message['state'] = 'ok'; + } else { + res_message['state'] = 'error'; + res_message['reason'] = handling_result[1]; + } + this.publish(this.ctx.i_chans['ctrl'], JSON.stringify(res_message)); + } + else { + let odf = this.ctx.o_chans.df(topic); + if (!odf) + return; + this.ctx.on_data(odf, JSON.parse(message)); + } } - ctx.app_id = params['id'] || _UUID(); + on_disconnect() { + console.info(`${this.ctx.name} (${this.ctx.app_id}) disconnected from ${this.ctx.url}.`); + if (this.ctx.on_disconnect) { + this.ctx.on_disconnect(); + } + } - let body = { - 'name': params['name'], - 'idf_list': params['idf_list'], - 'odf_list': params['odf_list'], - 'accept_protos': params['accept_protos'] || 'mqtt', - 'profile': params['profile'], - }; + register(url, params, callback) { + if (this.ctx.mqtt_client) { + throw new RegistrationError('Already registered'); + } - let _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; - if (typeof params['on_register'] != 'undefined' && typeof params['register_callback'] != 'undefined') { - throw new RegistrationError(_reg_msg); - } - else if (typeof params['on_register'] != 'undefined') { - ctx.on_register = params['on_register']; - } - else if (typeof params['register_callback'] != 'undefined') { - console.warning(_reg_msg); - ctx.on_register = params['register_callback']; - } + this.ctx.url = url; + if (!this.ctx.url || this.ctx.url == '') { + throw new RegistrationError(`Invalid url: ${this.ctx.url}`); + } - // other callbacks - ctx.on_deregister = params['on_deregister']; - ctx.on_connect = params['on_connect']; - ctx.on_disconnect = params['on_disconnect']; + this.ctx.app_id = params['id'] || _UUID(); + let body = { + 'name': params['name'], + 'idf_list': params['idf_list'], + 'odf_list': params['odf_list'], + 'accept_protos': params['accept_protos'] || 'mqtt', + 'profile': params['profile'], + }; - // filter out the empty `df_list`, in case of empty list, server reponsed 403. - ['idf_list', 'odf_list'].forEach( - x => { - if (Array.isArray(body[x]) && body[x].length == 0) - delete body[x]; + let _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; + if (params['on_register'] != undefined && params['register_callback'] != undefined) { + throw new RegistrationError(_reg_msg); + } + else if (params['on_register'] != undefined) { + this.ctx.on_register = params['on_register']; + } + else if (params['register_callback'] != undefined) { + console.warning(_reg_msg); + this.ctx.on_register = params['register_callback']; } - ); - - new Promise( - (resolve, reject) => { - superagent.put(`${ctx.url}/${ctx.app_id}`) - .type('json') - .accept('json') - .send(body) - .then(res => { - resolve(res); - }, err => { - reject(err); - }); - }) - .then(res => { - let metadata = res.body; - if (typeof metadata === 'string') { - metadata = JSON.parse(metadata); - } - ctx.name = metadata['name']; - ctx.mqtt_host = metadata['url']['host']; - ctx.mqtt_port = metadata['url']['ws_port']; - ctx.mqtt_username = metadata['username'] || ''; - ctx.mqtt_password = metadata['password'] || ''; - ctx.i_chans['ctrl'] = metadata['ctrl_chans'][0]; - ctx.o_chans['ctrl'] = metadata['ctrl_chans'][1]; - ctx.rev = metadata['rev']; - - ctx.mqtt_client = mqtt.connect(`${metadata.url['ws_scheme']}://${ctx.mqtt_host}:${ctx.mqtt_port}`, { - clientId: `iottalk-js-${ctx.app_id}`, - username: ctx.mqtt_username, - password: ctx.mqtt_password, - will: { - topic: ctx.i_chans['ctrl'], - // in most case of js DA, it never connect back - payload: JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), - retain: true, - }, - keepalive: 30, // seems 60 is problematic for default mosquitto setup - }); + // other callbacks + this.ctx.on_deregister = params['on_deregister']; + this.ctx.on_connect = params['on_connect']; + this.ctx.on_disconnect = params['on_disconnect']; + + + // filter out the empty `df_list`, in case of empty list, server reponsed 403. + ['idf_list', 'odf_list'].forEach( + x => { + if (Array.isArray(body[x]) && body[x].length == 0) + delete body[x]; + } + ); - ctx.mqtt_client.on('connect', (connack) => { - console.info('mqtt_connect'); - on_connect(); - if (callback) { - callback({ - 'metadata': metadata, - 'dan': ctx + new Promise( + (resolve, reject) => { + superagent.put(`${this.ctx.url}/${this.ctx.app_id}`) + .type('json') + .accept('json') + .send(body) + .then(res => { + resolve(res); + }, err => { + reject(err); }); + }) + .then(res => { + let metadata = res.body; + if (typeof metadata === 'string') { + metadata = JSON.parse(metadata); } - }); - ctx.mqtt_client.on('reconnect', () => { - console.info('mqtt_reconnect'); - }); - ctx.mqtt_client.on('disconnect', (packet) => { - console.info('mqtt_disconnect'); - on_disconnect(); - }); - ctx.mqtt_client.on('error', (error) => { - console.error('mqtt_error', error); - }); - ctx.mqtt_client.on('message', (topic, message, packet) => { - on_message(topic, message.toString()); // Convert message from Uint8Array to String - }); - ctx.on_signal = params['on_signal']; - ctx.on_data = params['on_data']; + this.ctx.name = metadata['name']; + this.ctx.mqtt_host = metadata['url']['host']; + this.ctx.mqtt_port = metadata['url']['ws_port']; + this.ctx.mqtt_username = metadata['username'] || ''; + this.ctx.mqtt_password = metadata['password'] || ''; + this.ctx.i_chans['ctrl'] = metadata['ctrl_chans'][0]; + this.ctx.o_chans['ctrl'] = metadata['ctrl_chans'][1]; + this.ctx.rev = metadata['rev']; + + this.ctx.mqtt_client = mqtt.connect(`${metadata.url['ws_scheme']}://${this.ctx.mqtt_host}:${this.ctx.mqtt_port}`, { + clientId: `iottalk-js-${this.ctx.app_id}`, + username: this.ctx.mqtt_username, + password: this.ctx.mqtt_password, + will: { + topic: this.ctx.i_chans['ctrl'], + // in most case of js DA, it never connect back + payload: JSON.stringify({ 'state': 'offline', 'rev': this.ctx.rev }), + retain: true, + }, + keepalive: 30, // seems 60 is problematic for default mosquitto setup + }); - setTimeout(() => { - if (!_first_publish) { - throw new RegistrationError('MQTT connection timeout'); - } - }, 5000); + this.ctx.mqtt_client.on('connect', (connack) => { + console.info('mqtt_connect'); + this.on_connect(); + if (callback) { + callback({ + 'metadata': metadata, + 'dan': this + }); + } + }); + this.ctx.mqtt_client.on('reconnect', () => { + console.info('mqtt_reconnect'); + }); + this.ctx.mqtt_client.on('disconnect', (packet) => { + console.info('mqtt_disconnect'); + this.on_disconnect(); + }); + this.ctx.mqtt_client.on('error', (error) => { + console.error('mqtt_error', error); + }); + this.ctx.mqtt_client.on('message', (topic, message, packet) => { + this.on_message(topic, message.toString()); // Convert message from Uint8Array to String + }); - if (ctx.on_register) { - ctx.on_register(); - } - }, err => { - console.error('on_failure', err); - if (callback) - callback(false, err); - return; - }); -} + this.ctx.on_signal = params['on_signal']; + this.ctx.on_data = params['on_data']; + + setTimeout(() => { + if (!this._first_publish) { + throw new RegistrationError('MQTT connection timeout'); + } + }, 5000); -export const deregister = function (callback) { - if (!ctx.mqtt_client) { - if (callback) - callback(false); - throw new RegistrationError('Not registered'); + if (this.ctx.on_register) { + this.ctx.on_register(); + } + }, err => { + console.error('on_failure', err); + if (callback) + callback(false, err); + }); } - publish( - ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'offline', 'rev': ctx.rev }), - true - ); - ctx.mqtt_client.end(); - - superagent.del(`${ctx.url}/${ctx.app_id}`) - .type('json') - .accept('json') - .send(JSON.stringify({ 'rev': ctx.rev })) - .then(res => { - ctx.mqtt_client = null; - if (ctx.on_deregister) { - ctx.on_deregister(); - } - if (callback) - return callback(true); - }, err => { - console.error('deregister fail', err); + deregister(callback) { + if (!this.ctx.mqtt_client) { if (callback) - return callback(false); - }); -} + callback(false); + throw new RegistrationError('Not registered'); + } -export const push = function (idf_name, data, qos) { - if (!ctx.mqtt_client || !_first_publish) { - throw new RegistrationError('Not registered'); - } - if (!ctx.i_chans.topic(idf_name)) { - return; + this.publish( + this.ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'offline', 'rev': this.ctx.rev }), + true + ); + this.ctx.mqtt_client.end(); + + superagent.del(`${this.ctx.url}/${this.ctx.app_id}`) + .type('json') + .accept('json') + .send(JSON.stringify({ 'rev': this.ctx.rev })) + .then(res => { + this.ctx.mqtt_client = null; + if (this.ctx.on_deregister) { + this.ctx.on_deregister(); + } + if (callback) + return callback(true); + }, err => { + console.error('deregister fail', err); + if (callback) + return callback(false); + }); } - if (qos === undefined) - qos = 1; - if (typeof data != 'object') { - data = [data]; - } + push(idf_name, data, qos) { + if (!this.ctx.mqtt_client || !this._first_publish) { + throw new RegistrationError('Not registered'); + } + if (!this.ctx.i_chans.topic(idf_name)) { + return; + } + if (qos === undefined) + qos = 1; + + if (typeof data != 'object') { + data = [data]; + } - publish(ctx.i_chans.topic(idf_name), JSON.stringify(data), false, qos); + this.publish(this.ctx.i_chans.topic(idf_name), JSON.stringify(data), false, qos); + } } -export const UUID = function () { - return ctx.app_id || _UUID(); +let _default_client = new Client(); + +export function register(url, params, callback) { + return _default_client.register(url, params, callback); } -export const connected = function () { - if (typeof ctx.mqtt_client !== 'object') return false; - return ctx.mqtt_client.connected; +export function deregister(callback) { + return _default_client.deregister(callback); } -export const reconnecting = function () { - if (typeof ctx.mqtt_client !== 'object') return false; - return ctx.mqtt_client.reconnecting; +export function push(idf_name, data, qos) { + return _default_client.push(idf_name, data, qos); } From 4767273b9da7736a750c748b2e046e596ca5315e Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 23 Mar 2021 16:46:00 +0800 Subject: [PATCH 16/30] remove old version callback --- src/dai.js | 7 +------ src/dan.js | 59 +++++++++++++++++++++--------------------------------- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/src/dai.js b/src/dai.js index 3f44ecb..8ca0e62 100644 --- a/src/dai.js +++ b/src/dai.js @@ -153,12 +153,7 @@ export default class { } }; - console.log('dai', msg); - - this.dan.register(this.api_url, msg, (result) => { - console.log('register', result); - document.title = this.device_name; - }); + this.dan.register(this.api_url, msg); window.onbeforeunload = function () { try { diff --git a/src/dan.js b/src/dan.js index a26ea39..4f39888 100644 --- a/src/dan.js +++ b/src/dan.js @@ -10,6 +10,8 @@ export class Client { this.ctx = new Context(); this._first_publish = false; this._is_reconnect = false; + this.on_connect = this.on_connect.bind(this); + this.on_disconnect = this.on_disconnect.bind(this); } publish(channel, message, retained, qos) { @@ -47,10 +49,13 @@ export class Client { } on_connect() { + console.info('mqtt_connect'); + if (!this._is_reconnect) { console.log(`Successfully connect to ${this.ctx.url}`); console.log(`Device ID: ${this.ctx.app_id}`); console.log(`Device name: ${this.ctx.name}.`); + document.title = this.ctx.name; this.subscribe(this.ctx.o_chans['ctrl'], (err, granted) => { if (err) { throw 'Subscribe to control channel failed'; @@ -87,17 +92,17 @@ export class Client { on_message(topic, message) { if (topic == this.ctx.o_chans['ctrl']) { - let signal = JSON.parse(message); + const signal = JSON.parse(message); let handling_result = null; switch (signal['command']) { case 'CONNECT': if ('idf' in signal) { - let idf = signal['idf']; + const idf = signal['idf']; this.ctx.i_chans.add(idf, signal['topic']); handling_result = this.ctx.on_signal(signal['command'], [idf]); } else if ('odf' in signal) { - let odf = signal['odf']; + const odf = signal['odf']; this.ctx.o_chans.add(odf, signal['topic']); handling_result = this.ctx.on_signal(signal['command'], [odf]); this.subscribe(this.ctx.o_chans.topic(odf)); @@ -105,12 +110,12 @@ export class Client { break; case 'DISCONNECT': if ('idf' in signal) { - let idf = signal['idf']; + const idf = signal['idf']; this.ctx.i_chans.remove_df(idf); handling_result = this.ctx.on_signal(signal['command'], [idf]); } else if ('odf' in signal) { - let odf = signal['odf']; + const odf = signal['odf']; this.unsubscribe(this.ctx.o_chans.topic(odf)); this.ctx.o_chans.remove_df(odf); handling_result = this.ctx.on_signal(signal['command'], [odf]); @@ -137,13 +142,14 @@ export class Client { } on_disconnect() { - console.info(`${this.ctx.name} (${this.ctx.app_id}) disconnected from ${this.ctx.url}.`); + console.info('mqtt_disconnect'); + console.info(`${this.ctx.name} (${this.ctx.app_id}) disconnected from ${this.ctx.url}.`); if (this.ctx.on_disconnect) { this.ctx.on_disconnect(); } } - register(url, params, callback) { + register(url, params) { if (this.ctx.mqtt_client) { throw new RegistrationError('Already registered'); } @@ -163,7 +169,7 @@ export class Client { 'profile': params['profile'], }; - let _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; + const _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; if (params['on_register'] != undefined && params['register_callback'] != undefined) { throw new RegistrationError(_reg_msg); } @@ -229,23 +235,11 @@ export class Client { keepalive: 30, // seems 60 is problematic for default mosquitto setup }); - this.ctx.mqtt_client.on('connect', (connack) => { - console.info('mqtt_connect'); - this.on_connect(); - if (callback) { - callback({ - 'metadata': metadata, - 'dan': this - }); - } - }); + this.ctx.mqtt_client.on('connect', this.on_connect); this.ctx.mqtt_client.on('reconnect', () => { console.info('mqtt_reconnect'); }); - this.ctx.mqtt_client.on('disconnect', (packet) => { - console.info('mqtt_disconnect'); - this.on_disconnect(); - }); + this.ctx.mqtt_client.on('disconnect', this.on_disconnect); this.ctx.mqtt_client.on('error', (error) => { console.error('mqtt_error', error); }); @@ -265,17 +259,14 @@ export class Client { if (this.ctx.on_register) { this.ctx.on_register(); } - }, err => { + }) + .catch(err => { console.error('on_failure', err); - if (callback) - callback(false, err); }); } - deregister(callback) { + deregister() { if (!this.ctx.mqtt_client) { - if (callback) - callback(false); throw new RegistrationError('Not registered'); } @@ -295,12 +286,8 @@ export class Client { if (this.ctx.on_deregister) { this.ctx.on_deregister(); } - if (callback) - return callback(true); }, err => { console.error('deregister fail', err); - if (callback) - return callback(false); }); } @@ -324,12 +311,12 @@ export class Client { let _default_client = new Client(); -export function register(url, params, callback) { - return _default_client.register(url, params, callback); +export function register(url, params) { + return _default_client.register(url, params); } -export function deregister(callback) { - return _default_client.deregister(callback); +export function deregister() { + return _default_client.deregister(); } export function push(idf_name, data, qos) { From 7c5372fb197d3043c9bf7982f7f5fde539beeef7 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Mon, 29 Mar 2021 00:36:29 +0800 Subject: [PATCH 17/30] change register arguments --- src/dai.js | 33 +++++++++++++++------------------ src/dan.js | 46 ++++++++++++++++++---------------------------- 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/src/dai.js b/src/dai.js index 8ca0e62..cf56c42 100644 --- a/src/dai.js +++ b/src/dai.js @@ -124,25 +124,25 @@ export default class { odf_list.push([df_name, df.df_type]); } - let msg = { - 'on_signal': this.on_signal, - 'on_data': this.on_data, - 'accept_protos': ['mqtt'], - 'id': this.device_addr, - 'idf_list': idf_list, - 'odf_list': odf_list, - 'name': this.device_name, - 'profile': { + this.dan.register( + this.api_url, + this.on_signal, + this.on_data, + this.device_addr, + this.device_name, + idf_list, + odf_list, + ['mqtt'], + { 'model': this.device_model, 'u_name': this.username, 'extra_setup_webpage': this.extra_setup_webpage, 'device_webpage': this.device_webpage, }, - 'register_callback': this.register_callback, - 'on_register': this.on_register, - 'on_deregister': this.on_deregister, - 'on_connect': this.on_connect, - 'on_disconnect': () => { + this.on_register, + this.on_deregister, + this.on_connect, + () => { for (const key in this.flags) { this.flags[key] = false; } @@ -150,10 +150,7 @@ export default class { if (on_disconnect) { return on_disconnect; } - } - }; - - this.dan.register(this.api_url, msg); + }); window.onbeforeunload = function () { try { diff --git a/src/dan.js b/src/dan.js index 4f39888..9ab645e 100644 --- a/src/dan.js +++ b/src/dan.js @@ -149,7 +149,9 @@ export class Client { } } - register(url, params) { + register(url, on_signal, on_data, id, name, + idf_list, odf_list, accept_protos, profile, + on_register, on_deregister, on_connect, on_disconnect) { if (this.ctx.mqtt_client) { throw new RegistrationError('Already registered'); } @@ -159,32 +161,20 @@ export class Client { throw new RegistrationError(`Invalid url: ${this.ctx.url}`); } - this.ctx.app_id = params['id'] || _UUID(); - + this.ctx.app_id = id || _UUID(); let body = { - 'name': params['name'], - 'idf_list': params['idf_list'], - 'odf_list': params['odf_list'], - 'accept_protos': params['accept_protos'] || 'mqtt', - 'profile': params['profile'], + 'name': name, + 'idf_list': idf_list, + 'odf_list': odf_list, + 'accept_protos': accept_protos || 'mqtt', + 'profile': profile, }; - const _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'; - if (params['on_register'] != undefined && params['register_callback'] != undefined) { - throw new RegistrationError(_reg_msg); - } - else if (params['on_register'] != undefined) { - this.ctx.on_register = params['on_register']; - } - else if (params['register_callback'] != undefined) { - console.warning(_reg_msg); - this.ctx.on_register = params['register_callback']; - } - // other callbacks - this.ctx.on_deregister = params['on_deregister']; - this.ctx.on_connect = params['on_connect']; - this.ctx.on_disconnect = params['on_disconnect']; + this.ctx.on_register = on_register; + this.ctx.on_deregister = on_deregister; + this.ctx.on_connect = on_connect; + this.ctx.on_disconnect = on_disconnect; // filter out the empty `df_list`, in case of empty list, server reponsed 403. @@ -247,8 +237,8 @@ export class Client { this.on_message(topic, message.toString()); // Convert message from Uint8Array to String }); - this.ctx.on_signal = params['on_signal']; - this.ctx.on_data = params['on_data']; + this.ctx.on_signal = on_signal; + this.ctx.on_data = on_data; setTimeout(() => { if (!this._first_publish) { @@ -301,7 +291,7 @@ export class Client { if (qos === undefined) qos = 1; - if (typeof data != 'object') { + if (!Array.isArray(data)) { data = [data]; } @@ -311,8 +301,8 @@ export class Client { let _default_client = new Client(); -export function register(url, params) { - return _default_client.register(url, params); +export function register(...args) { + return _default_client.register(...args); } export function deregister() { From 092ccf5d0208c777090f77e1edccb3a87de6521b Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 30 Mar 2021 10:06:14 +0800 Subject: [PATCH 18/30] change register arguments --- src/dai.js | 34 +++++++++++++++++++--------------- src/dan.js | 31 +++++++++++++++---------------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/dai.js b/src/dai.js index cf56c42..41c5489 100644 --- a/src/dai.js +++ b/src/dai.js @@ -124,25 +124,26 @@ export default class { odf_list.push([df_name, df.df_type]); } - this.dan.register( - this.api_url, - this.on_signal, - this.on_data, - this.device_addr, - this.device_name, - idf_list, - odf_list, - ['mqtt'], - { + let msg = { + 'url': this.api_url, + 'on_signal': this.on_signal, + 'on_data': this.on_data, + 'accept_protos': ['mqtt'], + 'id': this.device_addr, + 'idf_list': idf_list, + 'odf_list': odf_list, + 'name': this.device_name, + 'profile': { 'model': this.device_model, 'u_name': this.username, 'extra_setup_webpage': this.extra_setup_webpage, 'device_webpage': this.device_webpage, }, - this.on_register, - this.on_deregister, - this.on_connect, - () => { + 'register_callback': this.register_callback, + 'on_register': this.on_register, + 'on_deregister': this.on_deregister, + 'on_connect': this.on_connect, + 'on_disconnect': () => { for (const key in this.flags) { this.flags[key] = false; } @@ -150,7 +151,10 @@ export default class { if (on_disconnect) { return on_disconnect; } - }); + } + }; + + this.dan.register(msg); window.onbeforeunload = function () { try { diff --git a/src/dan.js b/src/dan.js index 9ab645e..7c1fee5 100644 --- a/src/dan.js +++ b/src/dan.js @@ -149,32 +149,31 @@ export class Client { } } - register(url, on_signal, on_data, id, name, - idf_list, odf_list, accept_protos, profile, - on_register, on_deregister, on_connect, on_disconnect) { + register(params) { if (this.ctx.mqtt_client) { throw new RegistrationError('Already registered'); } - this.ctx.url = url; + this.ctx.url = params['url']; if (!this.ctx.url || this.ctx.url == '') { throw new RegistrationError(`Invalid url: ${this.ctx.url}`); } - this.ctx.app_id = id || _UUID(); + this.ctx.app_id = params['id'] || _UUID(); + let body = { - 'name': name, - 'idf_list': idf_list, - 'odf_list': odf_list, - 'accept_protos': accept_protos || 'mqtt', - 'profile': profile, + 'name': params['name'], + 'idf_list': params['idf_list'], + 'odf_list': params['odf_list'], + 'accept_protos': params['accept_protos'] || 'mqtt', + 'profile': params['profile'], }; // other callbacks - this.ctx.on_register = on_register; - this.ctx.on_deregister = on_deregister; - this.ctx.on_connect = on_connect; - this.ctx.on_disconnect = on_disconnect; + this.ctx.on_register = params['on_register']; + this.ctx.on_deregister = params['on_deregister']; + this.ctx.on_connect = params['on_connect']; + this.ctx.on_disconnect = params['on_disconnect']; // filter out the empty `df_list`, in case of empty list, server reponsed 403. @@ -237,8 +236,8 @@ export class Client { this.on_message(topic, message.toString()); // Convert message from Uint8Array to String }); - this.ctx.on_signal = on_signal; - this.ctx.on_data = on_data; + this.ctx.on_signal = params['on_signal']; + this.ctx.on_data = params['on_data']; setTimeout(() => { if (!this._first_publish) { From 204fdda985f4e913f3bf49dd20378b404a8dadaa Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Tue, 30 Mar 2021 17:34:14 +0800 Subject: [PATCH 19/30] check if document exists --- src/dan.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dan.js b/src/dan.js index 7c1fee5..e530395 100644 --- a/src/dan.js +++ b/src/dan.js @@ -55,7 +55,9 @@ export class Client { console.log(`Successfully connect to ${this.ctx.url}`); console.log(`Device ID: ${this.ctx.app_id}`); console.log(`Device name: ${this.ctx.name}.`); - document.title = this.ctx.name; + if (typeof (document) !== "undefined") { + document.title = this.ctx.name; + } this.subscribe(this.ctx.o_chans['ctrl'], (err, granted) => { if (err) { throw 'Subscribe to control channel failed'; From 4622132885178f2a87e9b4acababe2ed542abf53 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Thu, 8 Apr 2021 16:00:10 +0800 Subject: [PATCH 20/30] remove new Promise --- src/dai.js | 1 + src/dan.js | 16 ++++------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/dai.js b/src/dai.js index 41c5489..e51cd8b 100644 --- a/src/dai.js +++ b/src/dai.js @@ -156,6 +156,7 @@ export default class { this.dan.register(msg); + // FIXME: window is not defined in node.js window.onbeforeunload = function () { try { if (!this.persistent_binding) { diff --git a/src/dan.js b/src/dan.js index e530395..df0ae5b 100644 --- a/src/dan.js +++ b/src/dan.js @@ -186,18 +186,10 @@ export class Client { } ); - new Promise( - (resolve, reject) => { - superagent.put(`${this.ctx.url}/${this.ctx.app_id}`) - .type('json') - .accept('json') - .send(body) - .then(res => { - resolve(res); - }, err => { - reject(err); - }); - }) + superagent.put(`${this.ctx.url}/${this.ctx.app_id}`) + .type('json') + .accept('json') + .send(body) .then(res => { let metadata = res.body; if (typeof metadata === 'string') { From 6366062659b33b5c565810eb414c042ec45dbeea Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Fri, 9 Apr 2021 00:40:25 +0800 Subject: [PATCH 21/30] subscribe return Promise --- src/dai.js | 2 +- src/dan.js | 81 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/dai.js b/src/dai.js index e51cd8b..67530e5 100644 --- a/src/dai.js +++ b/src/dai.js @@ -124,7 +124,7 @@ export default class { odf_list.push([df_name, df.df_type]); } - let msg = { + const msg = { 'url': this.api_url, 'on_signal': this.on_signal, 'on_data': this.on_data, diff --git a/src/dan.js b/src/dan.js index df0ae5b..d44a995 100644 --- a/src/dan.js +++ b/src/dan.js @@ -25,20 +25,33 @@ export class Client { if (qos === undefined) qos = 2; - this.ctx.mqtt_client.publish(channel, message, { - retain: retained, - qos: qos, - }); + this.ctx.mqtt_client.publish( + channel, + JSON.stringify(message), + { + retain: retained, + qos: qos, + } + ); } - subscribe(channel, callback, qos) { + subscribe(channel, qos) { if (!this.ctx.mqtt_client) return; if (qos === undefined) qos = 2; - return this.ctx.mqtt_client.subscribe(channel, { qos: qos }, callback); + return new Promise( + (resolve, reject) => { + this.ctx.mqtt_client.subscribe(channel, { qos: qos }, (err, granted) => { + if (err) { + reject(err); + } + resolve(); + }); + } + ); } unsubscribe(channel) { @@ -58,32 +71,42 @@ export class Client { if (typeof (document) !== "undefined") { document.title = this.ctx.name; } - this.subscribe(this.ctx.o_chans['ctrl'], (err, granted) => { - if (err) { - throw 'Subscribe to control channel failed'; - } - }); + this.subscribe(this.ctx.o_chans['ctrl']) + .then(() => { + this.ctx.i_chans.remove_all_df(); + this.ctx.o_chans.remove_all_df(); + + this.ctx.mqtt_client.publish( + this.ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'online', 'rev': this.ctx.rev }), + { retain: true, qos: 2, }, + (err) => { + if (!err) { + this._first_publish = true; + } + }); + }) + .catch(err => { + console.error('Subscribe to control channel failed'); + }); } else { console.info(`Reconnect: ${this.ctx.name}.`); this.publish( this.ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'offline', 'rev': this.ctx.rev }), + { 'state': 'offline', 'rev': this.ctx.rev }, true // retained message ); - } - this.ctx.i_chans.remove_all_df(); - this.ctx.o_chans.remove_all_df(); - this.ctx.mqtt_client.publish( - this.ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'online', 'rev': this.ctx.rev }), - { retain: true, qos: 2, }, - (err) => { - if (!err) { - this._first_publish = true; - } - }); + this.ctx.i_chans.remove_all_df(); + this.ctx.o_chans.remove_all_df(); + + this.ctx.mqtt_client.publish( + this.ctx.i_chans['ctrl'], + JSON.stringify({ 'state': 'online', 'rev': this.ctx.rev }), + { retain: true, qos: 2, }, + ); + } this._is_reconnect = true; @@ -124,7 +147,7 @@ export class Client { } break; } - let res_message = { + const res_message = { 'msg_id': signal['msg_id'], }; if (typeof handling_result === 'boolean' && handling_result) { @@ -133,7 +156,7 @@ export class Client { res_message['state'] = 'error'; res_message['reason'] = handling_result[1]; } - this.publish(this.ctx.i_chans['ctrl'], JSON.stringify(res_message)); + this.publish(this.ctx.i_chans['ctrl'], res_message); } else { let odf = this.ctx.o_chans.df(topic); @@ -163,7 +186,7 @@ export class Client { this.ctx.app_id = params['id'] || _UUID(); - let body = { + const body = { 'name': params['name'], 'idf_list': params['idf_list'], 'odf_list': params['odf_list'], @@ -255,7 +278,7 @@ export class Client { this.publish( this.ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'offline', 'rev': this.ctx.rev }), + { 'state': 'offline', 'rev': this.ctx.rev }, true ); this.ctx.mqtt_client.end(); @@ -288,7 +311,7 @@ export class Client { data = [data]; } - this.publish(this.ctx.i_chans.topic(idf_name), JSON.stringify(data), false, qos); + this.publish(this.ctx.i_chans.topic(idf_name), data, false, qos); } } From 7382e93e9779241b822fc97658bd260eeeabad4a Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Fri, 9 Apr 2021 20:02:57 +0800 Subject: [PATCH 22/30] use promise in on_connect() --- src/dan.js | 90 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/src/dan.js b/src/dan.js index d44a995..052c908 100644 --- a/src/dan.js +++ b/src/dan.js @@ -15,9 +15,8 @@ export class Client { } publish(channel, message, retained, qos) { - if (!this.ctx.mqtt_client) { + if (!this.ctx.mqtt_client) throw 'unable to publish without ctx.mqtt_client'; - } if (retained === undefined) retained = false; @@ -25,14 +24,18 @@ export class Client { if (qos === undefined) qos = 2; - this.ctx.mqtt_client.publish( - channel, - JSON.stringify(message), - { - retain: retained, - qos: qos, - } - ); + return new Promise((resolve, reject) => { + this.ctx.mqtt_client.publish(channel, + JSON.stringify(message), + { retain: retained, qos: qos, }, + (err) => { + if (err) { + reject(err); + } + resolve(); + } + ) + }); } subscribe(channel, qos) { @@ -42,28 +45,38 @@ export class Client { if (qos === undefined) qos = 2; - return new Promise( - (resolve, reject) => { - this.ctx.mqtt_client.subscribe(channel, { qos: qos }, (err, granted) => { + return new Promise((resolve, reject) => { + this.ctx.mqtt_client.subscribe(channel, + { qos: qos }, + (err, granted) => { if (err) { reject(err); } resolve(); }); - } - ); + }); } unsubscribe(channel) { if (!this.ctx.mqtt_client) return; - return this.ctx.mqtt_client.unsubscribe(channel); + return new Promise((resolve, reject) => { + this.ctx.mqtt_client.unsubscribe(channel, + (err) => { + if (err) { + reject(err); + } + resolve(); + }); + }); } on_connect() { console.info('mqtt_connect'); + let promise_thing; + if (!this._is_reconnect) { console.log(`Successfully connect to ${this.ctx.url}`); console.log(`Device ID: ${this.ctx.app_id}`); @@ -71,48 +84,40 @@ export class Client { if (typeof (document) !== "undefined") { document.title = this.ctx.name; } - this.subscribe(this.ctx.o_chans['ctrl']) - .then(() => { - this.ctx.i_chans.remove_all_df(); - this.ctx.o_chans.remove_all_df(); - - this.ctx.mqtt_client.publish( - this.ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'online', 'rev': this.ctx.rev }), - { retain: true, qos: 2, }, - (err) => { - if (!err) { - this._first_publish = true; - } - }); - }) + promise_thing = this.subscribe(this.ctx.o_chans['ctrl']) .catch(err => { - console.error('Subscribe to control channel failed'); + throw 'Subscribe to control channel failed'; }); } else { console.info(`Reconnect: ${this.ctx.name}.`); - this.publish( + promise_thing = this.publish( this.ctx.i_chans['ctrl'], { 'state': 'offline', 'rev': this.ctx.rev }, true // retained message ); + } + promise_thing.then(() => { this.ctx.i_chans.remove_all_df(); this.ctx.o_chans.remove_all_df(); - this.ctx.mqtt_client.publish( + this.publish( this.ctx.i_chans['ctrl'], - JSON.stringify({ 'state': 'online', 'rev': this.ctx.rev }), - { retain: true, qos: 2, }, - ); - } + { 'state': 'online', 'rev': this.ctx.rev }, + true // retained message + ).then(() => { + this._first_publish = true; + }); - this._is_reconnect = true; + this._is_reconnect = true; - if (this.ctx.on_connect) { - this.ctx.on_connect(); - } + if (this.ctx.on_connect) { + this.ctx.on_connect(); + } + }).catch(err => { + console.error(err); + }); } on_message(topic, message) { @@ -200,7 +205,6 @@ export class Client { this.ctx.on_connect = params['on_connect']; this.ctx.on_disconnect = params['on_disconnect']; - // filter out the empty `df_list`, in case of empty list, server reponsed 403. ['idf_list', 'odf_list'].forEach( x => { From 768fa7c3b95b14fc5ab343cd00170af076ff4306 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Sat, 10 Apr 2021 12:31:47 +0800 Subject: [PATCH 23/30] return reject in promise --- src/dan.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/dan.js b/src/dan.js index 052c908..ffd7fae 100644 --- a/src/dan.js +++ b/src/dan.js @@ -30,9 +30,9 @@ export class Client { { retain: retained, qos: qos, }, (err) => { if (err) { - reject(err); + return reject(err); } - resolve(); + return resolve(); } ) }); @@ -50,9 +50,9 @@ export class Client { { qos: qos }, (err, granted) => { if (err) { - reject(err); + return reject(err); } - resolve(); + return resolve(); }); }); } @@ -65,9 +65,9 @@ export class Client { this.ctx.mqtt_client.unsubscribe(channel, (err) => { if (err) { - reject(err); + return reject(err); } - resolve(); + return resolve(); }); }); } @@ -78,13 +78,15 @@ export class Client { let promise_thing; if (!this._is_reconnect) { - console.log(`Successfully connect to ${this.ctx.url}`); - console.log(`Device ID: ${this.ctx.app_id}`); - console.log(`Device name: ${this.ctx.name}.`); - if (typeof (document) !== "undefined") { - document.title = this.ctx.name; - } promise_thing = this.subscribe(this.ctx.o_chans['ctrl']) + .then(() => { + console.log(`Successfully connect to ${this.ctx.url}`); + console.log(`Device ID: ${this.ctx.app_id}`); + console.log(`Device name: ${this.ctx.name}.`); + if (typeof (document) !== "undefined") { + document.title = this.ctx.name; + } + }) .catch(err => { throw 'Subscribe to control channel failed'; }); From f6c68a95ef0342d0466bfb9a38184ad4f1fd2cf6 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Sat, 10 Apr 2021 20:02:08 +0800 Subject: [PATCH 24/30] remove disconnect info --- src/dan.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/dan.js b/src/dan.js index ffd7fae..5925126 100644 --- a/src/dan.js +++ b/src/dan.js @@ -32,7 +32,7 @@ export class Client { if (err) { return reject(err); } - return resolve(); + resolve(); } ) }); @@ -52,7 +52,7 @@ export class Client { if (err) { return reject(err); } - return resolve(); + resolve(); }); }); } @@ -67,7 +67,7 @@ export class Client { if (err) { return reject(err); } - return resolve(); + resolve(); }); }); } @@ -174,7 +174,6 @@ export class Client { } on_disconnect() { - console.info('mqtt_disconnect'); console.info(`${this.ctx.name} (${this.ctx.app_id}) disconnected from ${this.ctx.url}.`); if (this.ctx.on_disconnect) { this.ctx.on_disconnect(); @@ -331,6 +330,6 @@ export function deregister() { return _default_client.deregister(); } -export function push(idf_name, data, qos) { - return _default_client.push(idf_name, data, qos); +export function push(...args) { + return _default_client.push(...args); } From 99237ab127317b6e6e46cdbb133df677053f73de Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Sat, 10 Apr 2021 23:17:42 +0800 Subject: [PATCH 25/30] use df function in df_list --- examples/Dummy_Device/js/ida.js | 7 +++---- src/dai.js | 20 ++++++++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/ida.js index 5437d50..82439eb 100644 --- a/examples/Dummy_Device/js/ida.js +++ b/examples/Dummy_Device/js/ida.js @@ -15,12 +15,11 @@ $(function () { 'device_addr': 'c96ca71c-9e48-2a23-2868-acb420a2f105', 'device_name': 'Dummy', 'persistent_binding': true, - 'idf_list': [['Dummy_Sensor', ['int']]], - 'odf_list': ['Dummy_Control'], - 'df_function_list': [Dummy_Sensor, Dummy_Control], + 'idf_list': [[Dummy_Sensor, ['int']]], + 'odf_list': [Dummy_Control], 'push_interval': 0, 'interval': { - 'Dummy_Sensor': 1.5, + 'Dummy-Sensor': 1.5, } }; diff --git a/src/dai.js b/src/dai.js index 67530e5..20c4c87 100644 --- a/src/dai.js +++ b/src/dai.js @@ -34,12 +34,12 @@ export default class { } push_data(df_name) { - if (this.device_features[df_name].push_data == null) + if (this.device_features[this.df_func_name(df_name)].push_data == null) return; let _df_interval = this.interval[df_name] != undefined ? this.interval[df_name] : this.push_interval; console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} s]`); let _push_interval = setInterval(() => { - let _data = this.device_features[df_name].push_data(); + let _data = this.device_features[this.df_func_name(df_name)].push_data(); if (!this.flags[df_name]) { clearInterval(_push_interval); return; @@ -79,7 +79,7 @@ export default class { on_data(df_name, data) { try { - this.device_features[df_name].on_data(data); + this.device_features[this.df_func_name(df_name)].on_data(data); } catch (err) { console.error(err); return false; @@ -174,24 +174,20 @@ export default class { let param_type; let on_data; let push_data; - if (typeof option[`${typ}_list`][i] === 'string') { - df_name = option[`${typ}_list`][i]; + if (typeof option[`${typ}_list`][i] === 'function') { + df_name = option[`${typ}_list`][i].name; param_type = null; + on_data = push_data = option[`${typ}_list`][i]; } else if (typeof option[`${typ}_list`][i] === 'object' && option[`${typ}_list`][i].length == 2) { - df_name = option[`${typ}_list`][i][0]; + df_name = option[`${typ}_list`][i][0].name; param_type = option[`${typ}_list`][i][1]; + on_data = push_data = option[`${typ}_list`][i][0]; } else { throw new RegistrationError(`Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`); } - option['df_function_list'].forEach(df_function => { - if (this.df_func_name(df_name) == df_function.name) { - on_data = push_data = df_function; - } - }); - let df = new DeviceFeature({ 'df_name': df_name, 'df_type': typ, From 0d69e86d10316cfede2fce0ae96d4935a215e6e6 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Sun, 11 Apr 2021 13:19:02 +0800 Subject: [PATCH 26/30] Dummy Device --- examples/Dummy_Device/js/ida.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/ida.js index 82439eb..cc87ea8 100644 --- a/examples/Dummy_Device/js/ida.js +++ b/examples/Dummy_Device/js/ida.js @@ -19,7 +19,7 @@ $(function () { 'odf_list': [Dummy_Control], 'push_interval': 0, 'interval': { - 'Dummy-Sensor': 1.5, + 'Dummy_Sensor': 1.5, } }; From bc496b06cce9f09392823b1296bc953baf1a77e1 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Mon, 12 Apr 2021 12:37:56 +0800 Subject: [PATCH 27/30] use Array.isArray in parse_df_profile() --- src/dai.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/dai.js b/src/dai.js index 20c4c87..2c733e6 100644 --- a/src/dai.js +++ b/src/dai.js @@ -169,23 +169,24 @@ export default class { } parse_df_profile(option, typ) { - for (let i = 0; i < option[`${typ}_list`].length; i++) { + const df_list = `${typ}_list`; + for (let i = 0; i < option[df_list].length; i++) { let df_name; let param_type; let on_data; let push_data; - if (typeof option[`${typ}_list`][i] === 'function') { - df_name = option[`${typ}_list`][i].name; + if (!Array.isArray(option[df_list][i])) { + df_name = option[df_list][i].name; param_type = null; - on_data = push_data = option[`${typ}_list`][i]; + on_data = push_data = option[df_list][i]; } - else if (typeof option[`${typ}_list`][i] === 'object' && option[`${typ}_list`][i].length == 2) { - df_name = option[`${typ}_list`][i][0].name; - param_type = option[`${typ}_list`][i][1]; - on_data = push_data = option[`${typ}_list`][i][0]; + else if (Array.isArray(option[df_list][i]) && option[df_list][i].length == 2) { + df_name = option[df_list][i][0].name; + param_type = option[df_list][i][1]; + on_data = push_data = option[df_list][i][0]; } else { - throw new RegistrationError(`Invalid ${typ}_list, usage: [df_name, ...] or [[df_name, type], ...]`); + throw new RegistrationError(`Invalid ${df_list}, usage: [df_func, ...] or [[df_func, type], ...]`); } let df = new DeviceFeature({ From 8e2bf31b42d3a09dfcfb265822a7a8b26e10185e Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Mon, 12 Apr 2021 17:16:10 +0800 Subject: [PATCH 28/30] change ida to sa --- examples/Dummy_Device/index.html | 4 ++-- examples/Dummy_Device/js/{ida.js => sa.js} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename examples/Dummy_Device/js/{ida.js => sa.js} (100%) diff --git a/examples/Dummy_Device/index.html b/examples/Dummy_Device/index.html index f5f8f8a..8431a9e 100644 --- a/examples/Dummy_Device/index.html +++ b/examples/Dummy_Device/index.html @@ -5,7 +5,7 @@ Dummy_Device - + @@ -17,4 +17,4 @@ _____________ - + \ No newline at end of file diff --git a/examples/Dummy_Device/js/ida.js b/examples/Dummy_Device/js/sa.js similarity index 100% rename from examples/Dummy_Device/js/ida.js rename to examples/Dummy_Device/js/sa.js From 576f495d772b3675779f7a02fc58ea89ef5c9a74 Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" Date: Mon, 12 Apr 2021 17:19:38 +0800 Subject: [PATCH 29/30] use df_func_name() in parse_df_profile() --- src/dai.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dai.js b/src/dai.js index 2c733e6..5753d36 100644 --- a/src/dai.js +++ b/src/dai.js @@ -34,12 +34,12 @@ export default class { } push_data(df_name) { - if (this.device_features[this.df_func_name(df_name)].push_data == null) + if (this.device_features[df_name].push_data == null) return; let _df_interval = this.interval[df_name] != undefined ? this.interval[df_name] : this.push_interval; console.debug(`${df_name} : ${this.flags[df_name]} [message / ${_df_interval} s]`); let _push_interval = setInterval(() => { - let _data = this.device_features[this.df_func_name(df_name)].push_data(); + let _data = this.device_features[df_name].push_data(); if (!this.flags[df_name]) { clearInterval(_push_interval); return; @@ -79,7 +79,7 @@ export default class { on_data(df_name, data) { try { - this.device_features[this.df_func_name(df_name)].on_data(data); + this.device_features[df_name].on_data(data); } catch (err) { console.error(err); return false; @@ -88,8 +88,8 @@ export default class { } df_func_name(df_name) { - if (df_name.match(/-[A-Z]?(I|O)[0-9]?$/i)) { - return df_name.replace('-', '_'); + if (df_name.match(/_[A-Z]?(I|O)[0-9]?$/i)) { + return df_name.replace('_', '-'); } return df_name; } @@ -176,12 +176,12 @@ export default class { let on_data; let push_data; if (!Array.isArray(option[df_list][i])) { - df_name = option[df_list][i].name; + df_name = this.df_func_name(option[df_list][i].name); param_type = null; on_data = push_data = option[df_list][i]; } else if (Array.isArray(option[df_list][i]) && option[df_list][i].length == 2) { - df_name = option[df_list][i][0].name; + df_name = this.df_func_name(option[df_list][i][0].name); param_type = option[df_list][i][1]; on_data = push_data = option[df_list][i][0]; } From afbcb08a46e69c0e1c8d7df50990a84f7f0923aa Mon Sep 17 00:00:00 2001 From: "Guan Yu , Chen" <43653109+JasonChenGt@users.noreply.github.com> Date: Mon, 12 Apr 2021 17:24:48 +0800 Subject: [PATCH 30/30] Update index.html --- examples/Dummy_Device/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Dummy_Device/index.html b/examples/Dummy_Device/index.html index 8431a9e..c2be1e5 100644 --- a/examples/Dummy_Device/index.html +++ b/examples/Dummy_Device/index.html @@ -17,4 +17,4 @@ _____________ - \ No newline at end of file +