add Bluetooth Health Profile related API
authorCorentin Lecouvey <corentin.lecouvey@open.eurogiciel.org>
Fri, 11 Jul 2014 14:46:00 +0000 (16:46 +0200)
committerCorentin Lecouvey <corentin.lecouvey@open.eurogiciel.org>
Tue, 12 Aug 2014 12:05:35 +0000 (14:05 +0200)
bluetooth/bluetooth_api.js
bluetooth/bluetooth_instance_capi.cc
bluetooth/bluetooth_instance_capi.h
examples/bluetooth.html

index 413f10e..03d4a72 100644 (file)
@@ -56,7 +56,9 @@ function Adapter() {
   this.isReady = false;
   this.service_handlers = [];
   this.sockets = [];
-  this.change_listener = null;
+  this.change_listener = {};
+  this.health_apps = {};
+  this.health_channel_listener = {};
 }
 
 function validateAddress(address) {
@@ -758,9 +760,33 @@ BluetoothAdapter.prototype.setChangeListener = function(listener) {
 };
 
 BluetoothAdapter.prototype.unsetChangeListener = function() {
-  adapter.change_listener = null;
+  adapter.change_listener = {};
 };
 
+function BluetoothProfileHandler(profileType) {
+  _addConstProperty(this, 'profileType', profileType);
+}
+
+// BluetoothHealthProfileHandler class inherits from BluetoothProfileHandler class
+function BluetoothHealthProfileHandler() {
+  BluetoothProfileHandler.call(this, 'HEALTH');
+}
+BluetoothHealthProfileHandler.prototype = Object.create(BluetoothProfileHandler.prototype);
+BluetoothHealthProfileHandler.prototype.constructor = BluetoothHealthProfileHandler;
+
+BluetoothAdapter.prototype.getBluetoothProfileHandler = function(profile_type) {
+  if (!xwalk.utils.validateArguments('s', arguments)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  if (profile_type === 'HEALTH')
+    var profile_handler = new BluetoothHealthProfileHandler();
+  else
+    var profile_handler = new BluetoothProfileHandler(profile_type);
+
+  return profile_handler;
+}
+
 var _deviceClassMask = {
   'MINOR': 0x3F,
   'MAJOR': 0x1F,
@@ -1031,3 +1057,185 @@ BluetoothServiceHandler.prototype.unregister = function(successCallback, errorCa
       successCallback();
   });
 };
+
+function BluetoothHealthApplication(data_type, app_name, msg) {
+  _addConstProperty(this, 'dataType', data_type);
+  _addConstProperty(this, 'name', app_name);
+  this.onconnect = null;
+
+  if (msg)
+    this.app_id = msg.app_id;
+}
+
+BluetoothHealthApplication.prototype.unregister =
+    function(successCallback, errorCallback) {
+  if (!xwalk.utils.validateArguments('?ff', arguments)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  if (adapter.checkServiceAvailability(errorCallback))
+    return;
+
+  var msg = {
+    'cmd': 'UnregisterSinkApp',
+    'app_id': this.app_id
+  };
+
+  var app = this;
+
+  postMessage(msg, function(result) {
+    if (result.error != 0) {
+      if (errorCallback) {
+        var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
+        errorCallback(error);
+      }
+    }
+    if (app.app_id)
+      delete adapter.health_apps[app.app_id];
+
+    if (successCallback)
+      successCallback();
+  });
+}
+
+BluetoothHealthProfileHandler.prototype.registerSinkApplication =
+    function(dataType, name, successCallback, errorCallback) {
+  if (!xwalk.utils.validateArguments('nsf?f', arguments)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  if (adapter.checkServiceAvailability(errorCallback))
+    return;
+
+  var msg = {
+    'cmd': 'RegisterSinkApp',
+    'datatype': dataType
+  };
+
+  postMessage(msg, function(result) {
+    if (result.error != 0) {
+      if (errorCallback) {
+        var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
+        errorCallback(error);
+      }
+      return;
+    }
+
+    if (successCallback) {
+      var application = new BluetoothHealthApplication(dataType, name, result);
+      adapter.health_apps[result.app_id] = application;
+      successCallback(application);
+    }
+  });
+}
+
+BluetoothHealthProfileHandler.prototype.connectToSource =
+    function(peer, application, successCallback, errorCallback) {
+  if (!xwalk.utils.validateArguments('oof?f', arguments)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  if (adapter.checkServiceAvailability(errorCallback))
+    return;
+
+  var msg = {
+    'cmd': 'ConnectToSource',
+    'address': peer.address,
+    'app_id': application.app_id
+  };
+
+  postMessage(msg, function(result) {
+    if (result.error != 0) {
+      if (errorCallback) {
+        var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
+        if(result.error == 1)
+          error = new tizen.WebAPIError(tizen.WebAPIException.INVALID_VALUES_ERR);
+        errorCallback(error);
+      }
+      return;
+    }
+
+    if (successCallback) {
+      var i = adapter.indexOfDevice(adapter.known_devices, result.address);
+      var channel = new BluetoothHealthChannel(adapter.known_devices[i],
+          adapter.health_apps[result.app_id], result);
+      successCallback(channel);
+    }
+  });
+}
+
+function BluetoothHealthChannel(device, application, msg) {
+  _addConstProperty(this, 'peer', device);
+  _addConstProperty(this, 'channelType', (msg.channel_type == 1) ? 'RELIABLE' : 'STREAMING');
+  _addConstProperty(this, 'application', application);
+  _addConstProperty(this, 'isConnected', (msg.connected == 'true') ? true : false);
+  this.channel = msg.channel;
+  this.data = [];
+}
+
+BluetoothHealthChannel.prototype.close = function() {
+  if (adapter.checkServiceAvailability(errorCallback))
+    return;
+
+  var msg = {
+    'cmd': 'DisconnectSource',
+    'address': this.peer.address,
+    'channel': this.channel
+  };
+
+  var channel = this;
+
+  postMessage(msg, function(result) {
+    if (result.error != 0) {
+      if (errorCallback) {
+        var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
+        errorCallback(error);
+      }
+      return;
+    }
+
+    _addConstProperty(channel, 'isConnected', false);
+    if (adapter.health_channel_listener.onclose)
+      adapter.health_channel_listener.onclose();
+  });
+}
+
+BluetoothHealthChannel.prototype.sendData = function(data) {
+  if (adapter.checkServiceAvailability(errorCallback))
+    return;
+
+  if (!xwalk.utils.validateArguments('o', arguments)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  var msg = {
+    'cmd': 'SendHealthData',
+    'data': data,
+    'channel' : this.channel
+  };
+
+  postMessage(msg, function(result) {
+    if (result.error != 0) {
+      var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
+      return 0;
+    }
+
+    return result.size;
+  });
+}
+
+BluetoothHealthChannel.prototype.setListener = function(listener) {
+  if (!xwalk.utils.validateArguments('o', arguments)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  if (!xwalk.utils.validateObject(listener, 'ff', ['onmessage', 'onclose'])) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  adapter.health_channel_listener = listener;
+}
+
+BluetoothHealthChannel.prototype.unsetListener = function() {
+  adapter.health_channel_listener = {};
+}
index 0a0de32..25ca033 100644 (file)
@@ -19,9 +19,6 @@ BluetoothInstance::BluetoothInstance()
       adapter_enabled_(false),
       js_reply_needed_(false),
       stop_discovery_from_js_(false) {
-
-  CAPI(bt_initialize());
-  InitializeAdapter();
 }
 
 BluetoothInstance::~BluetoothInstance() {
@@ -29,6 +26,10 @@ BluetoothInstance::~BluetoothInstance() {
   CAPI(bt_deinitialize());
 }
 
+  CAPI(bt_initialize());
+  InitializeAdapter();
+}
+
 void BluetoothInstance::HandleMessage(const char* message) {
   picojson::value v;
 
@@ -56,6 +57,16 @@ void BluetoothInstance::HandleMessage(const char* message) {
     HandleCloseSocket(v);
   else if (cmd == "UnregisterServer")
     HandleUnregisterServer(v);
+  else if (cmd == "RegisterSinkApp")
+    HandleRegisterSinkApp(v);
+  else if (cmd == "UnregisterSinkApp")
+    HandleUnregisterSinkApp(v);
+  else if (cmd == "ConnectToSource")
+    HandleConnectToSource(v);
+  else if (cmd == "DisconnectSource")
+    HandleDisconnectSource(v);
+  else if (cmd == "SendHealthData")
+    HandleSendHealthData(v);
 }
 
 void BluetoothInstance::HandleSyncMessage(const char* message) {
@@ -305,7 +316,6 @@ void BluetoothInstance::OnBondCreated(int result, bt_device_info_s* device_info,
     return;
   }
 
-
   LOG_DBG("");
   picojson::value::object o;
   o["cmd"] = picojson::value("");
@@ -427,6 +437,71 @@ void BluetoothInstance::OnSocketHasData(bt_socket_received_data_s* data,
   obj->InternalPostMessage(picojson::value(o));
 }
 
+void BluetoothInstance::OnHdpConnected(int result, const char* remote_address,
+    const char* app_id, bt_hdp_channel_type_e type, unsigned int channel,
+    void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+
+  LOG_DBG("");
+  picojson::value::object o;
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["ConnectToSource"]);
+  obj->callbacks_id_map_.erase("ConnectToSource");
+
+  o["address"] = picojson::value(remote_address);
+  o["app_id"] = picojson::value(app_id);
+  o["channel_type"] = picojson::value(static_cast<double>(type));
+  o["channel"] = picojson::value(static_cast<double>(channel));
+  o["connected"] = picojson::value("true");
+  obj->InternalPostMessage(picojson::value(o));
+}
+
+void BluetoothInstance::OnHdpDisconnected(int result, const char* remote_address,
+    unsigned int channel, void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+
+  LOG_DBG("");
+  picojson::value::object o;
+  if (result)
+    o["error"] = picojson::value(static_cast<double>(1));
+  else
+    o["error"] = picojson::value(static_cast<double>(0));
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["DisconnectSource"]);
+  obj->callbacks_id_map_.erase("DisconnectSource");
+  o["address"] = picojson::value(remote_address);
+  o["channel"] = picojson::value(static_cast<double>(channel));
+  o["connected"] = picojson::value("false");
+  obj->InternalPostMessage(picojson::value(o));
+}
+
+void BluetoothInstance::OnHdpDataReceived(unsigned int channel, const char* data,
+    unsigned int size, void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+
+  LOG_DBG("");
+  picojson::value::object o;
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["SendHealthData"]);
+  obj->callbacks_id_map_.erase("SendHealthData");
+  o["channel"] = picojson::value(static_cast<double>(channel));
+  o["data"] = picojson::value(data);
+  o["size"] = picojson::value(static_cast<double>(size));
+  obj->InternalPostMessage(picojson::value(o));
+}
+
+
 gboolean BluetoothInstance::GetDefaultAdapter(gpointer user_data) {
   BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
   if (!obj) {
@@ -488,6 +563,11 @@ void BluetoothInstance::InitializeAdapter() {
   CAPI(bt_device_set_bond_created_cb(OnBondCreated, this));
   CAPI(bt_device_set_bond_destroyed_cb(OnBondDestroyed, this));
 
+  // Should also be socket_connected_cb...
+
+  CAPI(bt_hdp_set_connection_state_changed_cb(OnHdpConnected, OnHdpDisconnected, this));
+  CAPI(bt_hdp_set_data_received_cb(OnHdpDataReceived, this));
+
   bt_adapter_state_e state = BT_ADAPTER_DISABLED;
   CAPI(bt_adapter_get_state(&state));
 
@@ -510,6 +590,9 @@ void BluetoothInstance::UninitializeAdapter() {
   CAPI(bt_device_unset_bond_destroyed_cb());
   CAPI(bt_socket_unset_connection_state_changed_cb());
   CAPI(bt_socket_unset_data_received_cb());
+
+  CAPI(bt_hdp_unset_connection_state_changed_cb());
+  CAPI(bt_hdp_unset_data_received_cb());
 }
 
 void BluetoothInstance::HandleGetDefaultAdapter(const picojson::value& msg) {
@@ -662,7 +745,6 @@ void BluetoothInstance::HandleCloseSocket(const picojson::value& msg) {
   else
     o["error"] = picojson::value(static_cast<double>(1));
 
-
   o["cmd"] = picojson::value("");
   o["reply_id"] = msg.get("reply_id");
   o["capi"] = picojson::value(static_cast<double>(1));
@@ -676,6 +758,104 @@ void BluetoothInstance::HandleUnregisterServer(const picojson::value& msg) {
   CAPI(bt_socket_destroy_rfcomm(socket));
 }
 
+void BluetoothInstance::HandleRegisterSinkApp(const picojson::value& msg) {
+  picojson::value::object o;
+  int error = 0;
+
+  unsigned short data_type =
+      static_cast<unsigned short>(msg.get("datatype").get<double>());
+
+  LOG_DBG(data_type);
+
+  char* app_id = NULL;
+  CAPI_ERR(bt_hdp_register_sink_app(data_type, &app_id), error);
+  LOG_DBG(app_id);
+  if (!error) {
+    o["error"] = picojson::value(static_cast<double>(0));
+    o["app_id"] = picojson::value(app_id);
+  } else {
+    o["error"] = picojson::value(static_cast<double>(1));
+  }
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = msg.get("reply_id");
+  InternalPostMessage(picojson::value(o));
+}
+
+void BluetoothInstance::HandleUnregisterSinkApp(const picojson::value& msg) {
+  picojson::value::object o;
+  int error = 0;
+
+  LOG_DBG(msg.get("app_id").to_str());
+
+  CAPI_ERR(bt_hdp_unregister_sink_app(msg.get("app_id").to_str().c_str()), error);
+  if (!error)
+    o["error"] = picojson::value(static_cast<double>(0));
+  else
+    o["error"] = picojson::value(static_cast<double>(1));
+
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = msg.get("reply_id");
+  InternalPostMessage(picojson::value(o));
+}
+
+void BluetoothInstance::HandleConnectToSource(const picojson::value& msg) {
+  picojson::value::object o;
+  int error = 0;
+
+  CAPI_ERR(
+      bt_hdp_connect_to_source(msg.get("address").to_str().c_str(),
+                               msg.get("app_id").to_str().c_str()),
+      error);
+  if (error != BT_ERROR_NONE) {
+    o["error"] = picojson::value(static_cast<double>(2));
+    if (error == BT_ERROR_INVALID_PARAMETER)
+      o["error"] = picojson::value(static_cast<double>(1));
+    o["cmd"] = picojson::value("");
+    o["reply_id"] = msg.get("reply_id");
+    InternalPostMessage(picojson::value(o));
+  } else {
+    callbacks_id_map_["ConnectToSource"] = msg.get("reply_id").to_str();
+  }
+}
+
+void BluetoothInstance::HandleDisconnectSource(const picojson::value& msg) {
+  picojson::value::object o;
+  int error = 0;
+
+  int channel = static_cast<int>(msg.get("channel").get<double>());
+
+  CAPI_ERR(bt_hdp_disconnect(msg.get("address").to_str().c_str(), channel),
+           error);
+  if (error != BT_ERROR_NONE) {
+    o["error"] = picojson::value(static_cast<double>(1));
+    o["cmd"] = picojson::value("");
+    o["reply_id"] = msg.get("reply_id");
+    InternalPostMessage(picojson::value(o));
+  } else {
+    callbacks_id_map_["DisconnectSource"] = msg.get("reply_id").to_str();
+  }
+}
+
+void BluetoothInstance::HandleSendHealthData(const picojson::value& msg) {
+  picojson::value::object o;
+  int error = 0;
+
+  std::string data = msg.get("data").to_str();
+  int channel = static_cast<int>(msg.get("channel").get<double>());
+
+  CAPI_ERR(
+      bt_hdp_send_data(channel, data.c_str(), static_cast<int>(data.size())),
+      error);
+  if (error != BT_ERROR_NONE) {
+    o["error"] = picojson::value(static_cast<double>(1));
+    o["cmd"] = picojson::value("");
+    o["reply_id"] = msg.get("reply_id");
+    InternalPostMessage(picojson::value(o));
+  } else {
+    callbacks_id_map_["SendHealthData"] = msg.get("reply_id").to_str();
+  }
+}
+
 void BluetoothInstance::FlushPendingMessages() {
   // Flush previous pending messages.
   if (queue_.empty())
index cc875ef..18689c8 100644 (file)
@@ -52,6 +52,7 @@ class BluetoothInstance : public common::Instance {
   ~BluetoothInstance();
 
  private:
+  virtual void Initialize();
   virtual void HandleMessage(const char* msg);
   virtual void HandleSyncMessage(const char* msg);
 
@@ -69,6 +70,11 @@ class BluetoothInstance : public common::Instance {
   void HandleSocketWriteData(const picojson::value& msg);
   void HandleCloseSocket(const picojson::value& msg);
   void HandleUnregisterServer(const picojson::value& msg);
+  void HandleRegisterSinkApp(const picojson::value& msg);
+  void HandleUnregisterSinkApp(const picojson::value& msg);
+  void HandleConnectToSource(const picojson::value& msg);
+  void HandleDisconnectSource(const picojson::value& msg);
+  void HandleSendHealthData(const picojson::value& msg);
 
   void InternalPostMessage(picojson::value v);
   void InternalSetSyncReply(picojson::value v);
@@ -103,6 +109,16 @@ class BluetoothInstance : public common::Instance {
 
   static void OnSocketHasData(bt_socket_received_data_s* data, void* user_data);
 
+  static void OnHdpConnected(int result, const char* remote_address,
+      const char* app_id, bt_hdp_channel_type_e type, unsigned int channel,
+      void* user_data);
+
+  static void OnHdpDisconnected(int result, const char* remote_address,
+      unsigned int channel, void* user_data);
+
+  static void OnHdpDataReceived(unsigned int channel, const char* data,
+      unsigned int size, void* user_data);
+
   // Map JS reply_id to a C API callback
   std::map<std::string, std::string> callbacks_id_map_;
 
index dfd9f9d..26ecf52 100644 (file)
@@ -20,6 +20,9 @@
 <button id="button12">Click 12</button>
 <button id="button13">Click 13</button>
 <button id="button14">Click 14</button>
+<button id="button15">Click 15</button>
+<button id="button16">Click 16</button>
+<button id="button17">Click 17</button>
 
 <pre id="console"></pre>
 <script src="js/js-test-pre.js"></script>
@@ -55,6 +58,9 @@ var CHAT_SERVICE_UUID = "5BCE9431-6C75-32AB-AFE0-2EC108A30860";
 var chatServiceHandler = null;
 var clientSocket = null;
 
+var healthProfileHandler = null;
+var healthApp = null;
+
 function validateAddress(address) {
   if (typeof address !== 'string')
     return false;
@@ -282,6 +288,20 @@ function sendMessage(msg) {
   }
 }
 
+function healthRegisterSuccess(app) {
+  output.value += '\nRegistered application: ' + app.name;
+  healthApp = app;
+}
+
+function healthRegisterError(e) {
+  output.value += '\nFailed to register application: ' + e.message;
+};
+
+function healthUnregisterSuccess() {
+  output.value += '\nApplication: ' + healthApp.name + ' is unregistered';
+  healthApp = null;
+}
+
 var onErrorCallback = function(e) {
     output.value += '\n [ERROR] Failed: ' + e.message;
 };
@@ -362,4 +382,33 @@ handle("button14", "Send message", function() {
 
   sendMessage(message);
 });
+
+handle("button15", "getBluetoothProfileHandler", function() {
+  output.value += '\n\n## get Health profile handler ##';
+  healthProfileHandler = adapter.getBluetoothProfileHandler("HEALTH");
+  output.value += '\n profile handler returns: ' + healthProfileHandler.profileType;
+});
+
+handle("button16", "Register Sink App", function() {
+  output.value += '\n\n## register Sink Application: testSinkApp ##';
+  if (!healthProfileHandler) {
+      output.value += '\nPlease get profile handler first !!!';
+      return;
+  }
+  healthProfileHandler.registerSinkApplication(4100, "testSinkApp", healthRegisterSuccess, healthRegisterError);
+});
+
+handle("button17", "Unregister Sink App", function() {
+  output.value += '\n\n## Unregister Sink Application: testSinkApp ##';
+  if (!healthProfileHandler) {
+      output.value += '\nPlease get profile handler first !!!';
+      return;
+  }
+  if (!healthApp) {
+      output.value += '\nNo sink app registered: nothing to do...';
+      return;
+  }
+  healthApp.unregister(healthUnregisterSuccess);
+});
+
 </script>