From 03c1a19a94b95a3781ea5f7ee35dab61f6190a2e Mon Sep 17 00:00:00 2001
From: Jason Chen <cjs358a@gmail.com>
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 <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <title>Dummy_Device</title>
+  <script src="https://code.jquery.com/jquery-3.4.1.min.js"
+    integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
+  <script src="js/ida.js"></script>
+  <script src="../../build-web/iottalkjs-web.js"></script>
+
+</head>
+
+<body>
+  <legend>Dummy_Sensor</legend>
+  <legend class='IDF_value'>_____________</legend>
+  <legend>Dummy_Control</legend>
+  <legend class='ODF_value'>_____________</legend>
+</body>
+
+</html>
\ 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 @@
   <legend class='ODF_value'>_____________</legend>
 </body>
 
-</html>
\ No newline at end of file
+</html>

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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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" <cjs358a@gmail.com>
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 @@
   <title>Dummy_Device</title>
   <script src="https://code.jquery.com/jquery-3.4.1.min.js"
     integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
-  <script src="js/ida.js"></script>
+  <script src="js/sa.js"></script>
   <script src="../../build-web/iottalkjs-web.js"></script>
 
 </head>
@@ -17,4 +17,4 @@
   <legend class='ODF_value'>_____________</legend>
 </body>
 
-</html>
+</html>
\ 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" <cjs358a@gmail.com>
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 @@
   <legend class='ODF_value'>_____________</legend>
 </body>
 
-</html>
\ No newline at end of file
+</html>