BLE Central Console

このエントリーをはてなブックマークに追加

What's this

BLE Central console on Web.
You can search BLE nearby your obniz and connect to it.

Program

<!-- HTML Example -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
      integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">


  <script src="https://code.jquery.com/jquery-3.2.1.min.js"
          integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"
          integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"
          crossorigin="anonymous"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
          integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
          crossorigin="anonymous"></script>
  <script src="https://rawgit.com/sylvain-hamel/safarimobile-multiline-select/master/src/safarimobile-multiline-select.js" crossorigin="anonymous"></script>

  <script src="https://unpkg.com/obniz@2.0.2/obniz.js"></script>

  <style>
    select {
      width: 100%;
    }

    .row .col-4 {
      margin-bottom: 10px;
    }

    .row .round {
      border: 1px solid #ccc;
      border-radius: 5px;
      margin-left: 10px;
      margin-right: 10px;
      overflow-y: scroll;
      word-break: normal;
      height: 300px;
    }

    .row .round::-webkit-scrollbar {
      background:#eee;
      width:10px;
    }
    .row .round::-webkit-scrollbar-thumb {
      background:#aaa;
    }

    ul .input-group{
      padding-right: 40px;

    }

    /*for mobile safari*/
    ul.multilineselect{
      width:100%
    }
    ul.multilineselect::-webkit-scrollbar {
      background:#ccc;
      width:10px;
    }
    ul.multilineselect::-webkit-scrollbar-thumb {
      background:#aaa;
    }

  </style>


  
</head>

<body>
<div id="obniz-debug"></div>
<br>
<div class="text-center">
  <h1> Ble Central </h1>
</div>
<div class="container">
  <div class="row">
    <div class="col text-center">
    </div>
  </div>
  <div class="row">
    <div class="col-md-4 col-sm-6 col-6">
      <h3>Devices</h3>
      <select name="device" id="deviceSelector" class="form-control" size="8">
      </select><br/>
      <button type="button" id="device-clear" class="btn btn-secondary">clear</button>
      <button type="button" id="device-connect" class="btn btn-primary">Connect</button>
      <button type="button" id="device-disconnect" class="btn btn-danger" style="display:none">Disconnect</button>
    </div>
    <div class="col-md-4 col-sm-6 col-6">
      <h3>Services</h3>
      <select name="service" id="serviceSelector" class="form-control" size="8">
      </select>
    </div>
    <div class="col-md-4 col-sm-6 col-6">
      <h3>Characteristics</h3>
      <select name="characteristic" id="characteristicSelector"  class="form-control" size="8">
      </select>
    </div>
  </div>
  <div class="row">
    <div class="col-md-6 col-sm-12">
      <h3>detail</h3>
      <div class="round">
        <div id="device-detail" style="display:none">
          <ul>
            <li>device address</li>
            <span class="device_address"> </span>
            <li>rssi</li>
            <span class="device_rssi"> </span>
            <li>advertise data raw</li>
            <span class="adv_raw"> </span>
            <li>scan response data raw</li>
            <span class="scan_resp_raw"> </span>
            <li>advertise / scan response data meaning</li>
            <div class="meaning"> </div>
          </ul>
        </div>

        <div id="service-detail" style="display:none">
          <ul>
            <li>service uuid</li>
            <span class="service_uuid"> </span>
            <div class="service_name_wrapper">
              <li>uuid name (defined by <a href="https://www.bluetooth.com/specifications/gatt/services">Bluetooth specification</a>)</li>
              <span class="service_name"> </span>
            </div>
          </ul>
        </div>

        <div id="characteristic-detail" style="display:none">
          <ul>
            <li>characteristic uuid</li>
            <span class="characteristic_uuid"> </span>
            <div class="characteristic_name_wrapper">
              <li>uuid name (defined by <a href="https://www.bluetooth.com/specifications/gatt/characteristics">Bluetooth specification</a>)</li>
              <span class="characteristic_name_name"> </span>
            </div>
            <li>value type</li>
            <div class="btn-group btn-group-toggle" data-toggle="buttons">
              <label class="btn  btn-outline-primary active">
                <input type="radio" name="characteristic_type" autocomplete="off" value="binary" checked> binary
              </label>
              <label class="btn btn-outline-primary">
                <input type="radio" name="characteristic_type" autocomplete="off" value="text" > text
              </label>
            </div>
            <li>value</li>
            <span class="characteristic_value"> </span><br/>
            <button class="btn btn-primary mb-3" type="button" id="characteristic_read">read</button>
            <div class="input-group mb-3">
              <input type="text" class="form-control" id="characteristic_write_value" placeholder="hex string (ex: f94c8c...) " />
              <div class="input-group-append">
              <button class="btn btn-primary" type="button" id="characteristic_write">write</button>
            </div>
            </div>
          </ul>
        </div>
      </div>
    </div>
    <div class="col-md-6 col-sm-12">
      <h3>Log</h3>
      <div id="log" class="round">
        <div id="log-content">

        </div>
      </div>

    </div>
  </div>
</div>

<script>


  let isConnected = false;
  let devices = [];
  let deviceSelector =$("#deviceSelector");
  let serviceSelector =$("#serviceSelector");
  let characteristicSelector =$("#characteristicSelector");

  let deviceDetailTag = $("#device-detail");
  let serviceDetailTag = $("#service-detail");
  let characteristicDetailTag = $("#characteristic-detail");

  let logWrapperDom = $("#log");
  let logDom = $("#log-content");
  let currentPeripheral = null;


  serviceSelector.prop("disabled", true);
  characteristicSelector.prop("disabled", true);

  deviceDetailTag.hide();
  serviceDetailTag.hide();
  characteristicDetailTag.hide();


  $().ready(function () {
    if((navigator.userAgent.match(/Android/) && navigator.userAgent.match(/Mobile/))){
      $("select").fixForSafariMobile(true);

    }else{
      $("select").fixForSafariMobile();
    }
  });

  /* This will be over written on obniz.io webapp page */
  let obniz = new Obniz("OBNIZ_ID_HERE");


  /** BEFORE CONNECT **/
  obniz.onconnect = async function () {
    log("obniz connected.");

    log("start ble scan repeatly");
    startScanRepeatly();
  };

  function startScanRepeatly() {
    obniz.ble.scan.end();
    if (!isConnected) {
      log("scan repeating");
      obniz.ble.scan.start({duration: 30});
      setTimeout(startScanRepeatly, 35 * 1000);
    }
  }


  obniz.ble.scan.onfind = function (peripheral) {
    if (undefined === devices.find((elm) => {
          return elm.address === peripheral.address
        })) {
      let address = splitByLength(peripheral.address, 2).join(":");
      log("find new peripheral : " + address + (peripheral.localName? "(" + peripheral.localName + ")":""));
  
      devices.push(peripheral);
      let optionTag = '<option value="' + peripheral.address + '">' + address +(peripheral.localName? "(" + peripheral.localName + ")":"")  + '</option>';
      deviceSelector.append(optionTag);
      deviceSelector.trigger("item-added");
    }
  };


  $("#device-clear").on("click", () => {
    if(isConnected)return;
    obniz.ble.scan.end();
    devices = [];
    deviceSelector.empty();
    deviceSelector.trigger("item-added");

    log("clear all device data and rescan");
    obniz.ble.scan.start({duration: 30});
  });

  $("#device-disconnect").on("click", () => {
    currentPeripheral.disconnect();
  });

  $("#device-connect").on("click", () => {
    let device = getDevice(deviceSelector.val());
    device.onconnect = ()=>{
        log("connected to " + splitByLength(device.address, 2).join(":"));
      currentPeripheral = device;
      $("#device-connect").hide();
      $("#device-disconnect").show();

      serviceSelector.prop("disabled", false);
      characteristicSelector.prop("disabled", false);
      findService();
    };

    device.ondisconnect = ()=>{
      log("disconnected from " + splitByLength(device.address, 2).join(":"));
      isConnected = false;
      serviceSelector.prop("disabled", true);
      characteristicSelector.prop("disabled", true);
      deviceSelector.prop("disabled", false);

      $("#device-connect").prop("disabled", false);
      $("#device-clear").prop("disabled", false);

      currentPeripheral = null;
      $("#device-connect").show();
      $("#device-disconnect").hide();

      log("start ble scan repeatly");
      startScanRepeatly();
    };

    $("#device-connect").prop("disabled", true);
    $("#device-clear").prop("disabled", true);
    deviceSelector.prop("disabled", true);
    obniz.ble.scan.end();
    device.connect();
    log("connecting to " + splitByLength(device.address, 2).join(":"));
    isConnected = true;

  });

  deviceSelector.change(()=>{
    let device = getDevice(deviceSelector.val());
    showDetailDevice(device);
    serviceSelector.empty();
    serviceSelector.trigger("item-added");
    characteristicSelector.empty();
    characteristicSelector.trigger("item-added");
  });


  /** AFTER CONNECT **/


  function findService(){
    log("discovering services on device(" + splitByLength(currentPeripheral.address, 2).join(":") + ")");
    serviceSelector.empty();
    serviceSelector.trigger("item-added");
    currentPeripheral.discoverAllServices();
    currentPeripheral.ondiscoverservice = (service) =>{
      let optionTag = '<option value="' + service.uuid + '">' + service.uuid + '</option>';
      serviceSelector.append(optionTag);
      serviceSelector.trigger("item-added");
    };
  }

  serviceSelector.change(()=>{
    let service = currentPeripheral.getService(serviceSelector.val());
    showDetailService(service);
    characteristicSelector.empty();
    characteristicSelector.trigger("item-added");
    findCharacteristics(service);
  });

  function findCharacteristics(service){
    log("discovering characteristics on service("+service.uuid+")");
    characteristicSelector.empty();
    characteristicSelector.trigger("item-added");
    service.discoverAllCharacteristics();
    service.ondiscovercharacteristic = (chara)=>{
      let optionTag = '<option value="' + chara.uuid + '">' + chara.uuid + '</option>';
      characteristicSelector.append(optionTag);
      characteristicSelector.trigger("item-added");
    }
  }


  characteristicSelector.change(()=>{
    let chara = currentPeripheral.getService(serviceSelector.val()).getCharacteristic(characteristicSelector.val());
    showDetailCharacteristic(chara);

    chara.read();
    characteristicDetailTag.find(".characteristic_value").html();
    log("read value on charactaristic ("+chara.uuid+")");

    chara.onread = (data) =>{
      let currentChara = currentPeripheral.getService(serviceSelector.val()).getCharacteristic(characteristicSelector.val());
      if(chara === currentChara ){

        let type = $("[name=characteristic_type]:checked").val();
        let str;
        if(type === "binary") {
          str = "0x" + data.map((elm) => {
            return elm.toString(16).padStart(2, "0")
          }).join("");
          if (str.length === 2) {
            str = "null";
          }
        }else{
          str = String.fromCharCode.apply(null, data);
          if(str.length === 0){
            str = "null";
          }else{
            str = '"' + str + '"';
          }
        }
        characteristicDetailTag.find(".characteristic_value").html(str);
      }
    }
  });

  $("#characteristic_read").on('click', ()=>{
    let chara = currentPeripheral.getService(serviceSelector.val()).getCharacteristic(characteristicSelector.val())
    log("read value on charactaristic ("+chara.uuid+")");
    chara.read();
  });

  $("#characteristic_write").on('click', ()=>{
    let chara = currentPeripheral.getService(serviceSelector.val()).getCharacteristic(characteristicSelector.val())
    log("write value on charactaristic ("+chara.uuid+")");
    let valString = $("#characteristic_write_value").val();
    let type = $("input[name=characteristic_type]:checked").val();
    let data;
    if(type === "binary") {
      data = splitByLength(valString, 2).map((elm) => {
        return parseInt(elm, 16)
      });
    }else{
      data = [];
      for(let i =0; i< valString.length; i++){
        data.push(valString.charCodeAt(i));
      }
    }
    chara.write(data);

    chara.onwrite = (results)=>{
      chara.read();
    }
  });

  $( 'input[name=characteristic_type]:radio' ).change( ()=> {
    let chara = currentPeripheral.getService(serviceSelector.val()).getCharacteristic(characteristicSelector.val())
    chara.read();
    let type = $("input[name=characteristic_type]:checked").val();
    let data = {
      binary : "hex string (ex: f94c8c...)",
      text : "string (ex: hello world. )",
    }
    $("#characteristic_write_value").val("");
    $("#characteristic_write_value").prop("placeholder",data[type] );
    characteristicDetailTag.find(".characteristic_value").html();
  });

    /** DISPLAY **/

  function showDetailDevice(peripheral){

    deviceDetailTag.find(".device_address").html(splitByLength(peripheral.address, 2).join(":"));
    deviceDetailTag.find(".device_rssi").html(peripheral.rssi);
    deviceDetailTag.find(".adv_raw").html(array2string(peripheral.adv_data));
    deviceDetailTag.find(".scan_resp_raw").html(array2string(peripheral.scan_resp));

    peripheral.analyseAdvertisement();
    let meanings = [];
    meanings.push("<ul>");
    for( let row of peripheral.advertise_data_rows){

      let data = advDataAnalyze(row);
      meanings.push("<li>"+data.title+"</li>" + data.infomations.join("<br/>"));
    }
    meanings.push("</ul>");

    deviceDetailTag.find(".meaning").html(meanings.join(""));

    deviceDetailTag.show();
    serviceDetailTag.hide();
    characteristicDetailTag.hide();
  }


  function showDetailService(service) {
    const serviceUuidList = {
      0x1800: "Generic Access",
      0x1811: "Alert Notification Service",
      0x1815: "Automation IO",
      0x180F: "Battery Service",
      0x1810: "Blood Pressure",
      0x181B: "Body Composition",
      0x181E: "Bond Management Service",
      0x181F: "Continuous Glucose Monitoring",
      0x1805: "Current Time Service",
      0x1818: "Cycling Power",
      0x1816: "Cycling Speed and Cadence",
      0x180A: "Device Information",
      0x181A: "Environmental Sensing",
      0x1826: "Fitness Machine",
      0x1801: "Generic Attribute",
      0x1808: "Glucose",
      0x1809: "Health Thermometer",
      0x180D: "Heart Rate",
      0x1823: "HTTP Proxy",
      0x1812: "Human Interface Device",
      0x1802: "Immediate Alert",
      0x1821: "Indoor Positioning",
      0x1820: "Internet Protocol Support Service",
      0x1803: "Link Loss",
      0x1819: "Location and Navigation",
      0x1827: "Mesh Provisioning Service",
      0x1828: "Mesh Proxy Service",
      0x1807: "Next DST Change Service",
      0x1825: "Object Transfer Service",
      0x180E: "Phone Alert Status Service",
      0x1822: "Pulse Oximeter Service",
      0x1829: "Reconnection Configuration",
      0x1806: "Reference Time Update Service",
      0x1814: "Running Speed and Cadence",
      0x1813: "Scan Parameters",
      0x1824: "Transport Discovery",
      0x1804: "Tx Power",
      0x181C: "User Data",
      0x181D: "Weight Scale",
    };

    serviceDetailTag.find(".service_uuid").html(service.uuid);

    let name = serviceUuidList[service.uuid];
    if (name) {
      serviceDetailTag.find(".service_name_wrapper").show();
      serviceDetailTag.find(".service_name").html(name);
    } else {
      serviceDetailTag.find(".service_name_wrapper").hide();
      serviceDetailTag.find(".service_name").html("");
    }
    deviceDetailTag.hide();
    serviceDetailTag.show();
    characteristicDetailTag.hide();
  }


  function showDetailCharacteristic(chara) {
    const characteristicUuidList = {
      0x2A7E:"Aerobic Heart Rate Lower Limit",
      0x2A84:"Aerobic Heart Rate Upper Limit",
      0x2A7F:"Aerobic Threshold",
      0x2A80:"Age",
      0x2A5A:"Aggregate",
      0x2A43:"Alert Category ID",
      0x2A42:"Alert Category ID Bit Mask",
      0x2A06:"Alert Level",
      0x2A44:"Alert Notification Control Point",
      0x2A3F:"Alert Status",
      0x2AB3:"Altitude",
      0x2A81:"Anaerobic Heart Rate Lower Limit",
      0x2A82:"Anaerobic Heart Rate Upper Limit",
      0x2A83:"Anaerobic Threshold",
      0x2A58:"Analog",
      0x2A59:"Analog Output",
      0x2A73:"Apparent Wind Direction",
      0x2A72:"Apparent Wind Speed",
      0x2A01:"Appearance",
      0x2AA3:"Barometric Pressure Trend",
      0x2A19:"Battery Level",
      0x2A1B:"Battery Level State",
      0x2A1A:"Battery Power State",
      0x2A49:"Blood Pressure Feature",
      0x2A35:"Blood Pressure Measurement",
      0x2A9B:"Body Composition Feature",
      0x2A9C:"Body Composition Measurement",
      0x2A38:"Body Sensor Location",
      0x2AA4:"Bond Management Control Point",
      0x2AA5:"Bond Management Features",
      0x2A22:"Boot Keyboard Input Report",
      0x2A32:"Boot Keyboard Output Report",
      0x2A33:"Boot Mouse Input Report",
      0x2AA6:"Central Address Resolution",
      0x2AA8:"CGM Feature",
      0x2AA7:"CGM Measurement",
      0x2AAB:"CGM Session Run Time",
      0x2AAA:"CGM Session Start Time",
      0x2AAC:"CGM Specific Ops Control Point",
      0x2AA9:"CGM Status",
      0x2ACE:"Cross Trainer Data",
      0x2A5C:"CSC Feature",
      0x2A5B:"CSC Measurement",
      0x2A2B:"Current Time",
      0x2A66:"Cycling Power Control Point",
      0x2A65:"Cycling Power Feature",
      0x2A63:"Cycling Power Measurement",
      0x2A64:"Cycling Power Vector",
      0x2A99:"Database Change Increment",
      0x2A85:"Date of Birth",
      0x2A86:"Date of Threshold Assessment",
      0x2A08:"Date Time",
      0x2A0A:"Day Date Time",
      0x2A09:"Day of Week",
      0x2A7D:"Descriptor Value Changed",
      0x2A00:"Device Name",
      0x2A7B:"Dew Point",
      0x2A56:"Digital",
      0x2A57:"Digital Output",
      0x2A0D:"DST Offset",
      0x2A6C:"Elevation",
      0x2A87:"Email Address",
      0x2A0B:"Exact Time 100",
      0x2A0C:"Exact Time 256",
      0x2A88:"Fat Burn Heart Rate Lower Limit",
      0x2A89:"Fat Burn Heart Rate Upper Limit",
      0x2A26:"Firmware Revision String",
      0x2A8A:"First Name",
      0x2AD9:"Fitness Machine Control Point",
      0x2ACC:"Fitness Machine Feature",
      0x2ADA:"Fitness Machine Status",
      0x2A8B:"Five Zone Heart Rate Limits",
      0x2AB2:"Floor Number",
      0x2A8C:"Gender",
      0x2A51:"Glucose Feature",
      0x2A18:"Glucose Measurement",
      0x2A34:"Glucose Measurement Context",
      0x2A74:"Gust Factor",
      0x2A27:"Hardware Revision String",
      0x2A39:"Heart Rate Control Point",
      0x2A8D:"Heart Rate Max",
      0x2A37:"Heart Rate Measurement",
      0x2A7A:"Heat Index",
      0x2A8E:"Height",
      0x2A4C:"HID Control Point",
      0x2A4A:"HID Information",
      0x2A8F:"Hip Circumference",
      0x2ABA:"HTTP Control Point",
      0x2AB9:"HTTP Entity Body",
      0x2AB7:"HTTP Headers",
      0x2AB8:"HTTP Status Code",
      0x2ABB:"HTTPS Security",
      0x2A6F:"Humidity",
      0x2A2A:"IEEE 11073-20601 Regulatory Certification Data List",
      0x2AD2:"Indoor Bike Data",
      0x2AAD:"Indoor Positioning Configuration",
      0x2A36:"Intermediate Cuff Pressure",
      0x2A1E:"Intermediate Temperature",
      0x2A77:"Irradiance",
      0x2AA2:"Language",
      0x2A90:"Last Name",
      0x2AAE:"Latitude",
      0x2A6B:"LN Control Point",
      0x2A6A:"LN Feature",
      0x2AB1:"Local East Coordinate",
      0x2AB0:"Local North Coordinate",
      0x2A0F:"Local Time Information",
      0x2A67:"Location and Speed Characteristic",
      0x2AB5:"Location Name",
      0x2AAF:"Longitude",
      0x2A2C:"Magnetic Declination",
      0x2AA0:"Magnetic Flux Density - 2D",
      0x2AA1:"Magnetic Flux Density - 3D",
      0x2A29:"Manufacturer Name String",
      0x2A91:"Maximum Recommended Heart Rate",
      0x2A21:"Measurement Interval",
      0x2A24:"Model Number String",
      0x2A68:"Navigation",
      0x2A3E:"Network Availability",
      0x2A46:"New Alert",
      0x2AC5:"Object Action Control Point",
      0x2AC8:"Object Changed",
      0x2AC1:"Object First-Created",
      0x2AC3:"Object ID",
      0x2AC2:"Object Last-Modified",
      0x2AC6:"Object List Control Point",
      0x2AC7:"Object List Filter",
      0x2ABE:"Object Name",
      0x2AC4:"Object Properties",
      0x2AC0:"Object Size",
      0x2ABF:"Object Type",
      0x2ABD:"OTS Feature",
      0x2A04:"Peripheral Preferred Connection Parameters",
      0x2A02:"Peripheral Privacy Flag",
      0x2A5F:"PLX Continuous Measurement Characteristic",
      0x2A60:"PLX Features",
      0x2A5E:"PLX Spot-Check Measurement",
      0x2A50:"PnP ID",
      0x2A75:"Pollen Concentration",
      0x2A2F:"Position 2D",
      0x2A30:"Position 3D",
      0x2A69:"Position Quality",
      0x2A6D:"Pressure",
      0x2A4E:"Protocol Mode",
      0x2A62:"Pulse Oximetry Control Point",
      0x2A78:"Rainfall",
      0x2B1D:"RC Feature",
      0x2B1E:"RC Settings",
      0x2A03:"Reconnection Address",
      0x2B1F:"Reconnection Configuration Control Point",
      0x2A52:"Record Access Control Point",
      0x2A14:"Reference Time Information",
      0x2A3A:"Removable",
      0x2A4D:"Report",
      0x2A4B:"Report Map",
      0x2AC9:"Resolvable Private Address Only",
      0x2A92:"Resting Heart Rate",
      0x2A40:"Ringer Control point",
      0x2A41:"Ringer Setting",
      0x2AD1:"Rower Data",
      0x2A54:"RSC Feature",
      0x2A53:"RSC Measurement",
      0x2A55:"SC Control Point",
      0x2A4F:"Scan Interval Window",
      0x2A31:"Scan Refresh",
      0x2A3C:"Scientific Temperature Celsius",
      0x2A10:"Secondary Time Zone",
      0x2A5D:"Sensor Location",
      0x2A25:"Serial Number String",
      0x2A05:"Service Changed",
      0x2A3B:"Service Required",
      0x2A28:"Software Revision String",
      0x2A93:"Sport Type for Aerobic and Anaerobic Thresholds",
      0x2AD0:"Stair Climber Data",
      0x2ACF:"Step Climber Data",
      0x2A3D:"String",
      0x2AD7:"Supported Heart Rate Range",
      0x2AD5:"Supported Inclination Range",
      0x2A47:"Supported New Alert Category",
      0x2AD8:"Supported Power Range",
      0x2AD6:"Supported Resistance Level Range",
      0x2AD4:"Supported Speed Range",
      0x2A48:"Supported Unread Alert Category",
      0x2A23:"System ID",
      0x2ABC:"TDS Control Point",
      0x2A6E:"Temperature",
      0x2A1F:"Temperature Celsius",
      0x2A20:"Temperature Fahrenheit",
      0x2A1C:"Temperature Measurement",
      0x2A1D:"Temperature Type",
      0x2A94:"Three Zone Heart Rate Limits",
      0x2A12:"Time Accuracy",
      0x2A15:"Time Broadcast",
      0x2A13:"Time Source",
      0x2A16:"Time Update Control Point",
      0x2A17:"Time Update State",
      0x2A11:"Time with DST",
      0x2A0E:"Time Zone",
      0x2AD3:"Training Status",
      0x2ACD:"Treadmill Data",
      0x2A71:"True Wind Direction",
      0x2A70:"True Wind Speed",
      0x2A95:"Two Zone Heart Rate Limit",
      0x2A07:"Tx Power Level",
      0x2AB4:"Uncertainty",
      0x2A45:"Unread Alert Status",
      0x2AB6:"URI",
      0x2A9F:"User Control Point",
      0x2A9A:"User Index",
      0x2A76:"UV Index",
      0x2A96:"VO2 Max",
      0x2A97:"Waist Circumference",
      0x2A98:"Weight",
      0x2A9D:"Weight Measurement",
      0x2A9E:"Weight Scale Feature",
      0x2A79:"Wind Chill",
    };

    characteristicDetailTag.find(".characteristic_uuid").html(chara.uuid);

    let name = characteristicUuidList[chara.uuid];
    if (name) {
      characteristicDetailTag.find(".characteristic_name_wrapper").show();
      characteristicDetailTag.find(".characteristic_name").html(name);
    } else {
      characteristicDetailTag.find(".characteristic_name_wrapper").hide();
      characteristicDetailTag.find(".characteristic_name_wrapper").html("");
    }
    characteristicDetailTag.find('input[name=characteristic_type]:eq(0)').prop('checked', true);

    deviceDetailTag.hide();
    serviceDetailTag.hide();
    characteristicDetailTag.show();
  }
  /** UTIL **/

  function getDevice(address){
    return devices.filter((elm)=>{
      return elm.address === address;
    }).pop();
  }

  function log(msg) {
    let docHeight = logDom.outerHeight(); //ドキュメントの高さ
    let windowHeight = logWrapperDom.innerHeight(); //ウィンドウの高さ
    let pageBottom = docHeight - windowHeight; //ドキュメントの高さ - ウィンドウの高さ
    let needToBottomScroll = (pageBottom <= logWrapperDom.scrollTop());


    let before = logDom.html().trim();
    logDom.html(before.length > 0 ? before + "<br/>" + msg : msg);

    if (needToBottomScroll) {
      logWrapperDom.scrollTop(logDom.innerHeight());
    }


  }

  function splitByLength(str, length) {
    let resultArr = [];
    if (!str || !length || length < 1) {
      return resultArr;
    }
    let index = 0;
    let start = index;
    let end = start + length;
    while (start < str.length) {
      resultArr[index] = str.substring(start, end);
      index++;
      start = end;
      end = start + length;
    }
    return resultArr;
  }

  function array2string(arr){
    if(!arr || !Array.isArray(arr) ){
      return "undefined";
    }
    if(arr.length === 0 ){
      return "[ ]";
    }
    return "[" + arr.map((elm)=>{
      return "0x" + parseInt(elm).toString(16).padStart(2,"0");
    }).join(", ") + "]";
  }

  function advDataAnalyze(row){
    let title;
    let infomations = [];
    let bytes = row.slice(1);
    switch(row[0]) {
      case 0x01:
        title = "Flags";
        let data = {
          0x01:"LE Limited Discoverable Mode",
          0x02:"LE General Discoverable Mode",
          0x04:"BR/EDR Not Supported ",
          0x08:"Simultaneous LE and BR/EDR to Same Device Capa- ble (Controller)",
          0x10:"Simultaneous LE and BR/EDR to Same Device Capa- ble (Host)",
          0x20:"unknown flag - 0x20",
          0x40:"unknown flag - 0x40",
          0x80:"unknown flag - 0x80",
        };
        for(let key in data){
          if(parseInt(key) & bytes[0]){
            infomations.push(data[key]);
          }
        }
        break;
      case 0x02: // Incomplete List of 16-bit Service Class UUID
      case 0x03: // Complete List of 16-bit Service Class UUIDs
        title = "16-bit Service UUIDs";
        for (let j = 0; j < bytes.length; j += 2) {
          let uuid = bytes.slice(j, j + 2).toString('hex').match(/.{1,2}/g).reverse().join('');
          infomations.push("uuid - " + uuid);
        }
        break;

      case 0x06: // Incomplete List of 128-bit Service Class UUIDs
      case 0x07: // Complete List of 128-bit Service Class UUIDs
        title = "128-bit Service UUIDs";
        for (let j = 0; j < bytes.length; j += 16) {
          let uuid = bytes.slice(j, j + 16).toString('hex').match(/.{1,2}/g).reverse().join('');
          infomations.push("uuid - " + uuid);
        }
        break;

      case 0x08: // Shortened Local Name
      case 0x09: // Complete Local Name»
        title = "Local Name";
        infomations.push(String.fromCharCode.apply(null, bytes));
        break;

      case 0x0a: // Tx Power Level
        title = "Tx Power Level";
        infomations.push(bytes[0]);
        break;

      case  0x14: // List of 16 bit solicitation UUIDs
        title = "16-bit solicitation UUIDs";
        for (let j = 0; j < bytes.length; j += 2) {
          let uuid = bytes.slice(j, j + 2).toString('hex').match(/.{1,2}/g).reverse().join('');
          infomations.push("uuid - " + uuid);
        }

        break;

      case  0x15: // List of 128 bit solicitation UUIDs
        title = "128-bit solicitation UUIDs";
        for (let j = 0; j < bytes.length; j += 16) {
          let uuid = bytes.slice(j, j + 16).toString('hex').match(/.{1,2}/g).reverse().join('');
          infomations.push("uuid - " + uuid);
        }
        break;

      case 0x16: // 16-bit Service Data, there can be multiple occurences
        title = "16-bit Service Data";
        let serviceDataUuid = bytes.slice(0, 2).toString('hex').match(/.{1,2}/g).reverse().join('');
        let serviceData = bytes.slice(2, bytes.length);
        infomations.push("uuid - " + serviceDataUuid );
        infomations.push("serviceData - " + array2string(serviceData));
        break;

      case 0x20: // 32-bit Service Data, there can be multiple occurences
        title = "32-bit Service Data";
        let serviceData32Uuid = bytes.slice(0, 4).toString('hex').match(/.{1,2}/g).reverse().join('');
        let serviceData32 = bytes.slice(4, bytes.length);
        infomations.push("uuid - " + serviceData32Uuid + "<br/>serviceData - " + array2string(serviceData32));

        break;

      case 0x21: // 128-bit Service Data, there can be multiple occurences
        title = "128-bit Service Data";
        let serviceData128Uuid = bytes.slice(0, 16).toString('hex').match(/.{1,2}/g).reverse().join('');
        let serviceData128 = bytes.slice(16, bytes.length);
        infomations.push("uuid - " + serviceData128Uuid + "<br/>serviceData - " + array2string(serviceData128));

        break;


      case 0xff: // 128-bit Service Data, there can be multiple occurences
          if(bytes[0] === 0x4c
             && bytes[1] === 0x00
              && bytes[2] === 0x02
              && bytes[3] === 0x15
              && bytes.length === 25) {
            title = "Manufacturer Specific Data - iBeacon";
            let uuidData = bytes.slice(4, 20);
            let uuid = "";
            for(let i = 0; i< uuidData.length;i++){
              uuid = uuid +uuidData[i].toString(16).padStart(2,"0");
              if(i === (4-1) ||i === (4+2-1) ||i === (4+2*2-1) ||i === (4+2*3-1) ){
                uuid += "-";
              }
            }

            let major = "0x" + ((bytes[20]<<8) + bytes[21]).toString(16).padStart(4,"0");
            let minor = "0x" + ((bytes[22]<<8) + bytes[23]).toString(16).padStart(4,"0");
            let power = "0x" + (bytes[24]).toString(16).padStart(2,"0");


            infomations.push("uuid : " + uuid);
            infomations.push("major : " + major);
            infomations.push("minor : " + minor);
            infomations.push("power : " + power);

          }else{
            title = "Manufacturer Specific Data";
            infomations.push(array2string(row.slice(1)));
          }
        break;


      default :
        title = "unhandled type";
        infomations.push(array2string(row.slice(1)));
        break;

    }
    title += "(0x"+ row[0].toString(16).padStart(2,"0") +")";
    return {title, infomations};
  }

</script>
</body>
</html>

Run Now

The html will be opened to run a program.

You will Get in Few Days

Circuit for Starter “obniz Board” is available on Amazon and other online stores.
You can get it at below

Our products and resellers

Forum

Visit our developer’s forum to discuss and discover technologies.

Forum

Contact

Feel free to contact out support and technical team.

Contact us