diff --git a/java/android/com/bluekitchen/btstack/SocketConnectionUnix.java b/java/android/com/bluekitchen/btstack/SocketConnectionUnix.java new file mode 100644 index 000000000..8310bfe04 --- /dev/null +++ b/java/android/com/bluekitchen/btstack/SocketConnectionUnix.java @@ -0,0 +1,103 @@ +package com.bluekitchen.btstack; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import android.net.LocalSocket; +import android.net.LocalSocketAddress; + +public class SocketConnectionUnix extends SocketConnection { + + private LocalSocket socket; + private String unixSocketName = "/data/btstack/BTstack"; + private InputStream in; + private OutputStream out; + private byte inHeader[] = new byte[6]; + private byte inPayload[] = new byte[2000]; + + public SocketConnectionUnix(){ + socket = null; + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#connect() + */ + @Override + public boolean connect() { + try { + socket = new LocalSocket(); + LocalSocketAddress socketAddress = new LocalSocketAddress(unixSocketName, LocalSocketAddress.Namespace.FILESYSTEM); + socket.connect(socketAddress); + in = socket.getInputStream(); + out = socket.getOutputStream(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#sendPacket(com.bluekitchen.btstack.Packet) + */ + @Override + public boolean sendPacket(Packet packet) { + + if (out == null) return false; + + try { + System.out.println("Send "); Util.hexdump(packet.getBuffer(), packet.getPayloadLen()); + out.write(headerForPacket(packet)); + out.write(packet.getBuffer()); + out.flush(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#receivePacket() + */ + @Override + public Packet receivePacket() { + + if (in == null) return null; + + int bytes_read = Util.readExactly(in, inHeader, 0, 6); + if (bytes_read != 6) return null; + + int packetType = Util.readBt16(inHeader, 0); + int channel = Util.readBt16(inHeader, 2); + int len = Util.readBt16(inHeader, 4); + + Util.readExactly(in, inPayload, 0, len); + + Packet packet = new Packet(packetType, channel ,inPayload, len); + return packet; + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#disconnect() + */ + @Override + public void disconnect() { + + if (socket != null){ + try { + socket.close(); + } catch (IOException e) { + } + } + } + + private byte[] headerForPacket(Packet packet) { + byte header[] = new byte[6]; + Util.storeBt16(header, 0, packet.getPacketType()); + Util.storeBt16(header, 2, packet.getChannel()); + Util.storeBt16(header, 4, packet.getBuffer().length); + return header; + } +} diff --git a/java/android/com/bluekitchen/lescan/MainActivity.java b/java/android/com/bluekitchen/lescan/MainActivity.java new file mode 100644 index 000000000..f92943aa3 --- /dev/null +++ b/java/android/com/bluekitchen/lescan/MainActivity.java @@ -0,0 +1,296 @@ +package com.bluekitchen.lescan; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.widget.TextView; + +import com.bluekitchen.btstack.BD_ADDR; +import com.bluekitchen.btstack.BT_UUID; +import com.bluekitchen.btstack.BTstack; +import com.bluekitchen.btstack.GATTCharacteristic; +import com.bluekitchen.btstack.GATTService; +import com.bluekitchen.btstack.Packet; +import com.bluekitchen.btstack.PacketHandler; +import com.bluekitchen.btstack.Util; +import com.bluekitchen.btstack.event.BTstackEventState; +import com.bluekitchen.btstack.event.GAPLEAdvertisingReport; +import com.bluekitchen.btstack.event.GATTCharacteristicQueryResult; +import com.bluekitchen.btstack.event.GATTCharacteristicValueQueryResult; +import com.bluekitchen.btstack.event.GATTNotification; +import com.bluekitchen.btstack.event.GATTQueryComplete; +import com.bluekitchen.btstack.event.GATTServiceQueryResult; +import com.bluekitchen.btstack.event.HCIEventDisconnectionComplete; +import com.bluekitchen.btstack.event.HCIEventLEConnectionComplete; + +public class MainActivity extends Activity implements PacketHandler { + + private static final String BTSTACK_TAG = "BTstack"; + + private enum STATE { + w4_btstack_working, w4_scan_result, w4_connected, w4_services_complete, w4_characteristic_complete, w4_characteristic_read + , w4_characteristic_write, w4_acc_service_result, w4_acc_enable_characteristic_result, w4_write_acc_enable_result, w4_acc_client_config_characteristic_result, w4_acc_client_config_result, + w4_acc_data, w4_connected_acc + }; + + private TextView tv; + private BTstack btstack; + private STATE state; + private int testAddrType; + private BD_ADDR testAddr; + private int testHandle; + private GATTService testService; + private GATTCharacteristic testCharacteristic; + private int service_count = 0; + private int characteristic_count = 0; + + private byte[] acc_service_uuid = new byte[] {(byte)0xf0, 0, (byte)0xaa, (byte)0x10, 4, (byte)0x51, (byte)0x40, 0, (byte)0xb0, 0, 0, 0, 0, 0, 0, 0}; + private byte[] acc_chr_client_config_uuid = new byte[] {(byte)0xf0, 0, (byte)0xaa, (byte)0x11, 4, (byte)0x51, (byte)0x40, 0, (byte)0xb0, 0, 0, 0, 0, 0, 0, 0}; + private byte[] acc_chr_enable_uuid = new byte[] {(byte)0xf0, 0, (byte)0xaa, (byte)0x12, 4, (byte)0x51, (byte)0x40, 0, (byte)0xb0, 0, 0, 0, 0, 0, 0, 0}; + private byte[] acc_enable = new byte[] {1}; + private byte acc_notification = 1; + private GATTService accService; + private GATTCharacteristic enableCharacteristic; + private GATTCharacteristic configCharacteristic; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + tv = new TextView(this); + setContentView(tv); + + test(); + } + + void addMessage(final String message){ + Log.d(BTSTACK_TAG, message); + runOnUiThread(new Runnable(){ + public void run(){ + tv.append("\n"); + tv.append(message); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + public void handlePacket(Packet packet){ + +// System.out.println(packet.toString()); + if (packet instanceof HCIEventDisconnectionComplete){ + HCIEventDisconnectionComplete event = (HCIEventDisconnectionComplete) packet; + testHandle = event.getConnectionHandle(); + addMessage(String.format("Received disconnect, status %d, handle %x", event.getStatus(), testHandle)); + return; + } + + switch (state){ + case w4_btstack_working: + if (packet instanceof BTstackEventState){ + BTstackEventState event = (BTstackEventState) packet; + if (event.getState() == 2) { + + addMessage("GAPLEScanStart()"); + state = STATE.w4_scan_result; + btstack.GAPLEScanStart(); + } + } + break; + case w4_scan_result: + if (packet instanceof GAPLEAdvertisingReport){ + GAPLEAdvertisingReport report = (GAPLEAdvertisingReport) packet; + testAddrType = report.getAddressType(); + testAddr = report.getAddress(); + addMessage(String.format("Adv: type %d, addr %s", testAddrType, testAddr)); + addMessage(String.format("Data: %s", Util.asHexdump(report.getData()))); + addMessage("GAPLEScanStop()"); + btstack.GAPLEScanStop(); + addMessage("GAPLEConnect(...)"); + state = STATE.w4_connected_acc; + btstack.GAPLEConnect(testAddrType, testAddr); + } + break; + + case w4_connected: + if (packet instanceof HCIEventLEConnectionComplete){ + HCIEventLEConnectionComplete event = (HCIEventLEConnectionComplete) packet; + testHandle = event.getConnectionHandle(); + addMessage(String.format("Connection complete, status %d, handle %x", event.getStatus(), testHandle)); + state = STATE.w4_services_complete; + addMessage("GATTDiscoverPrimaryServices(...)"); + btstack.GATTDiscoverPrimaryServices(testHandle); + } + break; + case w4_services_complete: + if (packet instanceof GATTServiceQueryResult){ + GATTServiceQueryResult event = (GATTServiceQueryResult) packet; + if (testService == null){ + addMessage(String.format("First service UUID %s", event.getService().getUUID())); + testService = event.getService(); + } + Log.d(BTSTACK_TAG, "Service: " + event.getService()); + service_count++; + } + if (packet instanceof GATTQueryComplete){ + addMessage(String.format("Service query complete, total %d services", service_count)); + state = STATE.w4_characteristic_complete; + btstack.GATTDiscoverCharacteristicsForService(testHandle, testService); + } + break; + + case w4_characteristic_complete: + if (packet instanceof GATTCharacteristicQueryResult){ + GATTCharacteristicQueryResult event = (GATTCharacteristicQueryResult) packet; + if (testCharacteristic == null){ + addMessage(String.format("First characteristic UUID %s", event.getCharacteristic().getUUID())); + testCharacteristic = event.getCharacteristic(); + } + Log.d(BTSTACK_TAG, "Characteristic: " + event.getCharacteristic()); + characteristic_count++; + } + if (packet instanceof GATTQueryComplete){ + addMessage(String.format("Characteristic query complete, total %d characteristics", characteristic_count)); + state = STATE.w4_characteristic_read; + btstack.GATTReadValueOfCharacteristic(testHandle, testCharacteristic); + } + break; + + case w4_characteristic_read: + if (packet instanceof GATTCharacteristicValueQueryResult){ + addMessage("Read complete"); + Log.d(BTSTACK_TAG, packet.toString()); + state = STATE.w4_characteristic_write; + byte [] data = { 'B', 'T', 's', 't', 'a', 'c', 'k'}; + btstack.GATTWriteValueOfCharacteristic(testHandle, testCharacteristic, data.length, data); + } + break; + case w4_characteristic_write: + if (packet instanceof GATTQueryComplete){ + addMessage("Write complete, search for ACC service"); + state = STATE.w4_acc_service_result; + btstack.GATTDiscoverPrimaryServicesByUUID128(testHandle, new BT_UUID(this.acc_service_uuid)); // not working + } + break; + + case w4_connected_acc: + if (packet instanceof HCIEventLEConnectionComplete){ + HCIEventLEConnectionComplete event = (HCIEventLEConnectionComplete) packet; + testHandle = event.getConnectionHandle(); + addMessage(String.format("Connection complete, status %d, handle %x", event.getStatus(), testHandle)); + addMessage("Search for ACC service"); + state = STATE.w4_acc_service_result; + byte [] uuid = new byte[16]; + Util.flipX(this.acc_service_uuid, uuid); // works + btstack.GATTDiscoverPrimaryServicesByUUID128(testHandle, new BT_UUID(uuid)); + } + break; + + case w4_acc_service_result: + addMessage(String.format("w4_acc_service_result state")); + if (packet instanceof GATTServiceQueryResult){ + GATTServiceQueryResult event = (GATTServiceQueryResult) packet; + addMessage(String.format("ACC service found %s", event.getService().getUUID())); + accService = event.getService(); + break; + } + if (packet instanceof GATTQueryComplete){ + if (accService == null) { + addMessage("No acc service found"); + break; + } + addMessage("ACC Service found, searching for acc enable characteristic"); + state = STATE.w4_acc_enable_characteristic_result; + byte [] uuid = new byte[16]; + Util.flipX(this.acc_chr_enable_uuid, uuid); + btstack.GATTDiscoverCharacteristicsForServiceByUUID128(testHandle, accService, new BT_UUID(uuid)); + } + break; + + case w4_acc_enable_characteristic_result: + if (packet instanceof GATTCharacteristicQueryResult){ + GATTCharacteristicQueryResult event = (GATTCharacteristicQueryResult) packet; + enableCharacteristic = event.getCharacteristic(); + addMessage("Enable ACC Characteristic found "); + } + if (packet instanceof GATTQueryComplete){ + if (enableCharacteristic == null) { + addMessage("No acc enable chr found"); + break; + } + addMessage("Write enable acc characteristic"); + state = STATE.w4_write_acc_enable_result; + btstack.GATTWriteValueOfCharacteristic(testHandle, enableCharacteristic, 1, this.acc_enable); + } + break; + case w4_write_acc_enable_result: + if (packet instanceof GATTQueryComplete){ + addMessage("Acc enabled,searching for acc client config characteristic"); + byte [] uuid = new byte[16]; + Util.flipX(this.acc_chr_client_config_uuid, uuid); + btstack.GATTDiscoverCharacteristicsForServiceByUUID128(testHandle, accService, new BT_UUID(uuid)); + state = STATE.w4_acc_client_config_characteristic_result; + } + break; + + case w4_acc_client_config_characteristic_result: + if (packet instanceof GATTCharacteristicQueryResult){ + GATTCharacteristicQueryResult event = (GATTCharacteristicQueryResult) packet; + configCharacteristic = event.getCharacteristic(); + addMessage("ACC Client Config Characteristic found"); + } + if (packet instanceof GATTQueryComplete){ + if (configCharacteristic == null) { + addMessage("No acc config chr found"); + break; + } + addMessage("Write ACC Client Config Characteristic"); + state = STATE.w4_acc_data; + btstack.GATTWriteClientCharacteristicConfiguration(testHandle, configCharacteristic, this.acc_notification); + } + break; + + case w4_acc_data: + if (packet instanceof GATTQueryComplete){ + addMessage("Acc configured for notification"); + break; + } + + if (packet instanceof GATTNotification){ + addMessage("Acc Value"); + Log.d(BTSTACK_TAG, packet.toString()); + btstack.GAPDisconnect(testHandle); + } + + default: + break; + } + } + + void test(){ + + addMessage("LE Test Application"); + + btstack = new BTstack(); + btstack.registerPacketHandler(this); + + boolean ok = btstack.connect(); + if (!ok) { + addMessage("Failed to connect to BTstack Server"); + return; + } + + addMessage("BTstackSetPowerMode(1)"); + + state = STATE.w4_btstack_working; + btstack.BTstackSetPowerMode(1); + } +} diff --git a/java/build.xml b/java/build.xml new file mode 100644 index 000000000..fa91a8b39 --- /dev/null +++ b/java/build.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/example/com/bluekitchen/GATTClientTest.java b/java/example/com/bluekitchen/GATTClientTest.java new file mode 100644 index 000000000..f47e9afd3 --- /dev/null +++ b/java/example/com/bluekitchen/GATTClientTest.java @@ -0,0 +1,264 @@ +package com.bluekitchen; + +import com.bluekitchen.btstack.BD_ADDR; +import com.bluekitchen.btstack.BT_UUID; +import com.bluekitchen.btstack.BTstack; +import com.bluekitchen.btstack.GATTCharacteristic; +import com.bluekitchen.btstack.GATTService; +import com.bluekitchen.btstack.Packet; +import com.bluekitchen.btstack.PacketHandler; +import com.bluekitchen.btstack.Util; +import com.bluekitchen.btstack.event.BTstackEventState; +import com.bluekitchen.btstack.event.GAPLEAdvertisingReport; +import com.bluekitchen.btstack.event.GATTCharacteristicQueryResult; +import com.bluekitchen.btstack.event.GATTCharacteristicValueQueryResult; +import com.bluekitchen.btstack.event.GATTNotification; +import com.bluekitchen.btstack.event.GATTQueryComplete; +import com.bluekitchen.btstack.event.GATTServiceQueryResult; +import com.bluekitchen.btstack.event.HCIEventDisconnectionComplete; +import com.bluekitchen.btstack.event.HCIEventLEConnectionComplete; + +public class GATTClientTest implements PacketHandler { + + private enum STATE { + w4_btstack_working, w4_scan_result, w4_connected, w4_services_complete, w4_characteristic_complete, w4_characteristic_read + , w4_characteristic_write, w4_acc_service_result, w4_acc_enable_characteristic_result, w4_write_acc_enable_result, w4_acc_client_config_characteristic_result, w4_acc_client_config_result, + w4_acc_data, w4_connected_acc + }; + + private BTstack btstack; + private STATE state; + private int testAddrType; + private BD_ADDR testAddr; + private int testHandle; + private GATTService testService; + private GATTCharacteristic testCharacteristic; + private int service_count = 0; + private int characteristic_count = 0; + + private byte[] acc_service_uuid = new byte[] {(byte)0xf0, 0, (byte)0xaa, (byte)0x10, 4, (byte)0x51, (byte)0x40, 0, (byte)0xb0, 0, 0, 0, 0, 0, 0, 0}; + private byte[] acc_chr_client_config_uuid = new byte[] {(byte)0xf0, 0, (byte)0xaa, (byte)0x11, 4, (byte)0x51, (byte)0x40, 0, (byte)0xb0, 0, 0, 0, 0, 0, 0, 0}; + private byte[] acc_chr_enable_uuid = new byte[] {(byte)0xf0, 0, (byte)0xaa, (byte)0x12, 4, (byte)0x51, (byte)0x40, 0, (byte)0xb0, 0, 0, 0, 0, 0, 0, 0}; + private byte[] acc_enable = new byte[] {1}; + private byte acc_notification = 1; + private GATTService accService; + private GATTCharacteristic enableCharacteristic; + private GATTCharacteristic configCharacteristic; + + + public void handlePacket(Packet packet){ + +// System.out.println(packet.toString()); + if (packet instanceof HCIEventDisconnectionComplete){ + HCIEventDisconnectionComplete event = (HCIEventDisconnectionComplete) packet; + testHandle = event.getConnectionHandle(); + System.out.println(String.format("Received disconnect, status %d, handle %x", event.getStatus(), testHandle)); + return; + } + + switch (state){ + case w4_btstack_working: + if (packet instanceof BTstackEventState){ + BTstackEventState event = (BTstackEventState) packet; + if (event.getState() == 2) { + + System.out.println("GAPLEScanStart()"); + state = STATE.w4_scan_result; + btstack.GAPLEScanStart(); + } + } + break; + case w4_scan_result: + if (packet instanceof GAPLEAdvertisingReport){ + GAPLEAdvertisingReport report = (GAPLEAdvertisingReport) packet; + testAddrType = report.getAddressType(); + testAddr = report.getAddress(); + System.out.println(String.format("Adv: type %d, addr %s", testAddrType, testAddr)); + System.out.println(String.format("Data: %s", Util.asHexdump(report.getData()))); + System.out.println("GAPLEScanStop()"); + btstack.GAPLEScanStop(); + System.out.println("GAPLEConnect(...)"); + state = STATE.w4_connected_acc; + btstack.GAPLEConnect(testAddrType, testAddr); + } + break; + + case w4_connected: + if (packet instanceof HCIEventLEConnectionComplete){ + HCIEventLEConnectionComplete event = (HCIEventLEConnectionComplete) packet; + testHandle = event.getConnectionHandle(); + System.out.println(String.format("Connection complete, status %d, handle %x", event.getStatus(), testHandle)); + state = STATE.w4_services_complete; + System.out.println("GATTDiscoverPrimaryServices(...)"); + btstack.GATTDiscoverPrimaryServices(testHandle); + } + break; + case w4_services_complete: + if (packet instanceof GATTServiceQueryResult){ + GATTServiceQueryResult event = (GATTServiceQueryResult) packet; + if (testService == null){ + System.out.println(String.format("First service UUID %s", event.getService().getUUID())); + testService = event.getService(); + } + System.out.println("Service: " + event.getService()); + service_count++; + } + if (packet instanceof GATTQueryComplete){ + System.out.println(String.format("Service query complete, total %d services", service_count)); + state = STATE.w4_characteristic_complete; + btstack.GATTDiscoverCharacteristicsForService(testHandle, testService); + } + break; + + case w4_characteristic_complete: + if (packet instanceof GATTCharacteristicQueryResult){ + GATTCharacteristicQueryResult event = (GATTCharacteristicQueryResult) packet; + if (testCharacteristic == null){ + System.out.println(String.format("First characteristic UUID %s", event.getCharacteristic().getUUID())); + testCharacteristic = event.getCharacteristic(); + } + System.out.println("Characteristic: " + event.getCharacteristic()); + characteristic_count++; + } + if (packet instanceof GATTQueryComplete){ + System.out.println(String.format("Characteristic query complete, total %d characteristics", characteristic_count)); + state = STATE.w4_characteristic_read; + btstack.GATTReadValueOfCharacteristic(testHandle, testCharacteristic); + } + break; + + case w4_characteristic_read: + if (packet instanceof GATTCharacteristicValueQueryResult){ + System.out.println("Read complete"); + System.out.println( packet.toString()); + state = STATE.w4_characteristic_write; + byte [] data = { 'B', 'T', 's', 't', 'a', 'c', 'k'}; + btstack.GATTWriteValueOfCharacteristic(testHandle, testCharacteristic, data.length, data); + } + break; + case w4_characteristic_write: + if (packet instanceof GATTQueryComplete){ + System.out.println("Write complete, search for ACC service"); + state = STATE.w4_acc_service_result; + btstack.GATTDiscoverPrimaryServicesByUUID128(testHandle, new BT_UUID(this.acc_service_uuid)); // not working + } + break; + + case w4_connected_acc: + if (packet instanceof HCIEventLEConnectionComplete){ + HCIEventLEConnectionComplete event = (HCIEventLEConnectionComplete) packet; + testHandle = event.getConnectionHandle(); + System.out.println(String.format("Connection complete, status %d, handle %x", event.getStatus(), testHandle)); + System.out.println("Search for ACC service"); + state = STATE.w4_acc_service_result; + byte [] uuid = new byte[16]; + Util.flipX(this.acc_service_uuid, uuid); // works + btstack.GATTDiscoverPrimaryServicesByUUID128(testHandle, new BT_UUID(uuid)); + } + break; + + case w4_acc_service_result: + System.out.println(String.format("w4_acc_service_result state")); + if (packet instanceof GATTServiceQueryResult){ + GATTServiceQueryResult event = (GATTServiceQueryResult) packet; + System.out.println(String.format("ACC service found %s", event.getService().getUUID())); + accService = event.getService(); + break; + } + if (packet instanceof GATTQueryComplete){ + if (accService == null) { + System.out.println("No acc service found"); + break; + } + System.out.println("ACC Service found, searching for acc enable characteristic"); + state = STATE.w4_acc_enable_characteristic_result; + byte [] uuid = new byte[16]; + Util.flipX(this.acc_chr_enable_uuid, uuid); + btstack.GATTDiscoverCharacteristicsForServiceByUUID128(testHandle, accService, new BT_UUID(uuid)); + } + break; + + case w4_acc_enable_characteristic_result: + if (packet instanceof GATTCharacteristicQueryResult){ + GATTCharacteristicQueryResult event = (GATTCharacteristicQueryResult) packet; + enableCharacteristic = event.getCharacteristic(); + System.out.println("Enable ACC Characteristic found "); + } + if (packet instanceof GATTQueryComplete){ + if (enableCharacteristic == null) { + System.out.println("No acc enable chr found"); + break; + } + System.out.println("Write enable acc characteristic"); + state = STATE.w4_write_acc_enable_result; + btstack.GATTWriteValueOfCharacteristic(testHandle, enableCharacteristic, 1, this.acc_enable); + } + break; + case w4_write_acc_enable_result: + if (packet instanceof GATTQueryComplete){ + System.out.println("Acc enabled,searching for acc client config characteristic"); + byte [] uuid = new byte[16]; + Util.flipX(this.acc_chr_client_config_uuid, uuid); + btstack.GATTDiscoverCharacteristicsForServiceByUUID128(testHandle, accService, new BT_UUID(uuid)); + state = STATE.w4_acc_client_config_characteristic_result; + } + break; + + case w4_acc_client_config_characteristic_result: + if (packet instanceof GATTCharacteristicQueryResult){ + GATTCharacteristicQueryResult event = (GATTCharacteristicQueryResult) packet; + configCharacteristic = event.getCharacteristic(); + System.out.println("ACC Client Config Characteristic found"); + } + if (packet instanceof GATTQueryComplete){ + if (configCharacteristic == null) { + System.out.println("No acc config chr found"); + break; + } + System.out.println("Write ACC Client Config Characteristic"); + state = STATE.w4_acc_data; + btstack.GATTWriteClientCharacteristicConfiguration(testHandle, configCharacteristic, this.acc_notification); + } + break; + + case w4_acc_data: + if (packet instanceof GATTQueryComplete){ + System.out.println("Acc configured for notification"); + break; + } + + if (packet instanceof GATTNotification){ + System.out.println("Acc Value"); + System.out.println(packet.toString()); + btstack.GAPDisconnect(testHandle); + } + + default: + break; + } + } + + void test(){ + + System.out.println("LE Test Application"); + + // connect to BTstack Daemon via default port on localhost + btstack = new BTstack(); + btstack.setTcpPort(BTstack.DEFAULT_TCP_PORT); + btstack.registerPacketHandler(this); + boolean ok = btstack.connect(); + if (!ok) { + System.out.println("Failed to connect to BTstack Server"); + return; + } + + System.out.println("BTstackSetPowerMode(1)"); + + state = STATE.w4_btstack_working; + btstack.BTstackSetPowerMode(1); + } + + public static void main(String args[]){ + new GATTClientTest().test(); + } +} diff --git a/java/generate.py b/java/generate.py new file mode 100755 index 000000000..11d4c499d --- /dev/null +++ b/java/generate.py @@ -0,0 +1,434 @@ +#!/usr/bin/env python +# BlueKtichen GmbH (c) 2014 + +import glob +import re +import sys +import os + +print ''' +Java binding generator for BTstack +Copyright 2014, BlueKitchen GmbH +''' + +# com.bluekitchen.btstack.BTstack.java templates +java_btstack_header = \ +'''/** + * BTstack Client Library + */ + +package %s; + +public class BTstack extends BTstackClient { + +''' +java_btstack_command = ''' + public boolean %s(%s){ + // %s + int command_len = %s; + byte[] command = new byte[command_len]; + Util.storeBt16(command, 0, Util.opcode(%s, %s)); + int offset = 2; + Util.storeByte(command, offset, command_len - 3); + offset++; +%s + Packet packet = new Packet(Packet.HCI_COMMAND_PACKET, 0, command, command.length); + return sendPacket(packet); + } +''' +java_btstack_footer = ''' +} +''' + +# com.bluekitchen.btstack.EventFactory template +java_event_factory_template = \ +'''package {0}; + +import {0}.event.*; + +public class EventFactory {{ + + /** @brief event codes */ + +{1} + public static Event eventForPacket(Packet packet){{ + int eventType = Util.readByte(packet.getBuffer(), 0); + switch (eventType){{ +{2} + case 0x3e: // LE_META_EVENT + int subEventType = Util.readByte(packet.getBuffer(), 2); + switch (subEventType){{ +{3} + default: + return new Event(packet); + }} + + default: + return new Event(packet); + }} + }} +}} +''' +java_event_factory_event = ''' + case {0}: + return new {1}(packet); +''' +java_event_factory_subevent = ''' + case {0}: + return new {1}(packet); +''' + +# com.bluekitchen.btstack.events.* template +java_event_template = \ +'''package {0}.event; + +import {0}.*; + +public class {1} extends Event {{ + + public {1}(Packet packet) {{ + super(packet); + }} + {2} + {3} +}} +''' + +java_event_getter = \ +''' + public {0} get{1}(){{ + {2} + }} +''' + +java_event_getter_data = \ +'''int len = get{0}(); + byte[] result = new byte[len]; + System.arraycopy(data, {1}, result, 0, len); + return result;''' + +java_event_to_string = \ +''' + public String toString(){{ + StringBuffer t = new StringBuffer(); + t.append("{0} < type = "); + t.append(String.format("0x%02x, ", getEventType())); + t.append(getEventType()); +{1} t.append(" >"); + return t.toString(); + }} +''' + +# global variables/defines +package='com.bluekitchen.btstack' +gen_path = 'gen/' + package.replace('.', '/') +hci_cmds_h_path = '../include/btstack/hci_cmds.h' +hci_cmds_c_path = '../src/hci_cmds.c' +hci_h_path = '../src/hci.h' + +defines = dict() +defines_used = set() + +def assert_dir(path): + if not os.access(path, os.R_OK): + os.makedirs(path) + +def cap(x): + if x.lower() == 'btstack': + return 'BTstack' + acronyms = ['GAP', 'GATT', 'HCI', 'L2CAP', 'LE', 'RFCOMM', 'SM', 'SDP', 'UUID16', 'UUID128'] + if x.upper() in acronyms: + return x.upper() + return x.capitalize() + +def camel_case(name): + return ''.join(map(cap, name.split('_'))) + +def camel_case_var(name): + if name in ['uuid128', 'uuid16']: + return name + camel = camel_case(name) + return camel[0].lower() + camel[1:] + +def read_defines(infile): + global defines + with open (infile, 'rb') as fin: + + for line in fin: + parts = re.match('#define\s+(\w+)\s+(\w*)',line) + if parts and len(parts.groups()) == 2: + (key, value) = parts.groups() + defines[key] = value + +def java_type_for_btstack_type(type): + param_types = { '1' : 'int', '2' : 'int', '3' : 'int', '4' : 'long', 'H' : 'int', 'B' : 'BD_ADDR', + 'D' : 'byte []', 'E' : 'byte [] ', 'N' : 'String' , 'P' : 'byte []', 'A' : 'byte []', 'S' : 'byte []', + 'J' : 'int', 'L' : 'int', 'V' : 'byte []', 'U' : 'BT_UUID', + 'X' : 'GATTService', 'Y' : 'GATTCharacteristic', 'Z' : 'GATTCharacteristicDescriptor' } + return param_types[type] + +def size_for_type(type): + param_sizes = { '1' : 1, '2' : 2, '3' : 3, '4' : 4, 'H' : 2, 'B' : 6, 'D' : 8, 'E' : 240, 'N' : 248, 'P' : 16, + 'A' : 31, 'S' : -1, 'V': -1, 'J' : 1, 'L' : 2, 'U' : 16, 'X' : 20, 'Y' : 24, 'Z' : 18} + return param_sizes[type] + +def create_command_java(fout, name, ogf, ocf, format, params): + global java_btstack_command + + ind = ' ' + param_store = { + '1' : 'Util.storeByte(command, offset, %s);', + 'J' : 'Util.storeByte(command, offset, %s);', + '2' : 'Util.storeBt16(command, offset, %s);', + 'H' : 'Util.storeBt16(command, offset, %s);', + 'L' : 'Util.storeBt16(command, offset, %s);', + '3' : 'Util.storeBt24(command, offset, %s);', + '4' : 'Util.storeBt32(command, offset, %s);', + 'D' : 'Util.storeBytes(command, offset, %s, 8);', + 'E' : 'Util.storeBytes(command, offset, %s, 240);', + 'P' : 'Util.storeBytes(command, offset, %s, 16);', + 'A' : 'Util.storeBytes(command, offset, %s, 31);', + 'S' : 'Util.storeBytes(command, offset, %s);', + 'B' : 'Util.storeBytes(command, offset, %s.getBytes());', + 'U' : 'Util.storeBytes(command, offset, %s.getBytes());', + 'X' : 'Util.storeBytes(command, offset, %s.getBytes());', + 'Y' : 'Util.storeBytes(command, offset, %s.getBytes());', + 'Z' : 'Util.storeBytes(command, offset, %s.getBytes());', + 'N' : 'Util.storeString(command, offset, %s, 248);', + } + # method arguments + arg_counter = 1 + args = [] + for param_type, arg_name in zip(format, params): + arg_type = java_type_for_btstack_type(param_type) + arg_size = size_for_type(param_type) + arg = (param_type, arg_type, arg_size, arg_name) + args.append(arg) + arg_counter += 1 + + # method argument declaration + args2 = [] + for arg in args: + args2.append('%s %s' % (arg[1], arg[3])) + args_string = ', '.join(args2) + + # command size (opcode, len) + size_fixed = 3 + size_var = '' + for arg in args: + size = arg[2] + if size > 0: + size_fixed += size + else: + size_var += ' + %s.length' % arg[3] + size_string = '%u%s' % (size_fixed, size_var) + + store_params = '' + + length_name = '' + for (param_type, arg_type, arg_size, arg_name) in args: + if param_type in ['L', 'J']: + length_name = arg_name + if param_type == 'V': + store_params += ind + 'Util.storeBytes(command, offset, %s, %s);' % (arg_name, length_name) + '\n'; + store_params += ind + 'offset += %s;\n' % length_name; + length_name = '' + else: + store_params += ind + (param_store[param_type] % arg_name) + '\n'; + size = arg_size + if size > 0: + store_params += ind + 'offset += %u;\n' % arg_size; + else: + store_params += ind + 'offset += %s.length;\n' % arg_name + + fout.write( java_btstack_command % (name, args_string, format, size_string, ogf, ocf, store_params)) + +def mark_define_as_used(term): + if term.startswith('0'): + return + defines_used.add(term) + +def java_define_string(key): + global defines + return ' public static final int %s = %s;\n' % (key, defines[key]) + +def java_defines_string(keys): + return '\n'.join( map(java_define_string, sorted(keys))) + +def parse_commands(infile): + + global gen_path + assert_dir(gen_path) + + outfile = '%s/BTstack.java' % gen_path + + with open(outfile, 'w') as fout: + + fout.write(java_btstack_header % package) + + with open (infile, 'rb') as fin: + + params = [] + for line in fin: + + parts = re.match('.*@param\s*(\w*)\s*', line) + if parts and len(parts.groups()) == 1: + param = parts.groups()[0] + params.append(camel_case_var(param)) + continue + + declaration = re.match('const\s+hci_cmd_t\s+(\w+)[\s=]+', line) + if declaration: + command_name = camel_case(declaration.groups()[0]) + if command_name.endswith('Cmd'): + command_name = command_name[:-len('Cmd')] + continue + + definition = re.match('\s*OPCODE\\(\s*(\w+)\s*,\s+(\w+)\s*\\)\s*,\s\\"(\w*)\\".*', line) + if definition: + (ogf, ocf, format) = definition.groups() + if len(params) != len(format): + params = [] + arg_counter = 1 + for f in format: + arg_name = 'arg%u' % arg_counter + params.append(arg_name) + arg_counter += 1 + create_command_java(fout, command_name, ogf, ocf, format, params); + mark_define_as_used(ogf) + mark_define_as_used(ocf) + params = [] + continue + + fout.write('\n /** defines used */\n\n') + for key in sorted(defines_used): + fout.write(java_define_string(key)) + + fout.write(java_btstack_footer) + +def create_event(event_name, format, args): + global gen_path + global package + global java_event_template + + param_read = { + '1' : 'return Util.readByte(data, %u);', + 'J' : 'return Util.readByte(data, %u);', + '2' : 'return Util.readBt16(data, %u);', + '3' : 'return Util.readBt24(data, %u);', + '4' : 'return Util.readBt32(data, %u);', + 'H' : 'return Util.readBt16(data, %u);', + 'L' : 'return Util.readByte(data, %u);', + 'B' : 'return Util.readBdAddr(data, %u);', + 'X' : 'return Util.readGattService(data, %u);', + 'Y' : 'return Util.readGattCharacteristic(data, %u);', + 'Z' : 'return Util.readGattCharacteristicDescriptor(data, %u);', + # 'D' : 'Util.storeBytes(data, %u, 8);', + # 'E' : 'Util.storeBytes(data, %u, 240);', + # 'N' : 'Util.storeString(data, %u, 248);', + # 'P' : 'Util.storeBytes(data, %u, 16);', + # 'A' : 'Util.storeBytes(data, %u, 31);', + # 'S' : 'Util.storeBytes(data, %u);' + } + + gen_event_path = '%s/event' % gen_path + outfile = '%s/%s.java' % (gen_event_path, event_name) + with open(outfile, 'w') as fout: + offset = 2 + getters = '' + length_name = '' + for f, arg in zip(format, args): + # just remember name + if f in ['L','J']: + length_name = camel_case(arg) + if f == 'V': + access = java_event_getter_data.format(length_name, offset) + size = 0 + else: + access = param_read[f] % offset + size = size_for_type(f) + getters += java_event_getter.format(java_type_for_btstack_type(f), camel_case(arg), access) + offset += size + to_string_args = '' + for arg in args: + to_string_args += ' t.append(", %s = ");\n' % arg + to_string_args += ' t.append(get%s());\n' % camel_case(arg) + to_string_method = java_event_to_string.format(event_name, to_string_args) + fout.write(java_event_template.format(package, event_name, getters, to_string_method)) + +def create_events(events): + global gen_path + gen_path_events = gen_path + '/event' + assert_dir(gen_path_events) + + for event_type, event_name, format, args in events: + event_name = camel_case(event_name) + create_event(event_name, format, args) + +def create_event_factory(events, le_events, defines): + global gen_path + global package + global java_event_factory_event + global java_event_factory_template + + outfile = '%s/EventFactory.java' % gen_path + + cases = '' + for event_type, event_name, format, args in events: + event_name = camel_case(event_name) + cases += java_event_factory_event.format(event_type, event_name) + subcases = '' + for event_type, event_name, format, args in le_events: + event_name = camel_case(event_name) + subcases += java_event_factory_subevent.format(event_type, event_name) + + with open(outfile, 'w') as fout: + defines_text = java_defines_string(defines) + fout.write(java_event_factory_template.format(package, defines_text, cases, subcases)) + +def parse_events(path): + global gen_path + events = [] + le_events = [] + params = [] + event_types = set() + format = None + with open (path, 'rb') as fin: + for line in fin: + parts = re.match('.*@format\s*(\w*)\s*', line) + if parts and len(parts.groups()) == 1: + format = parts.groups()[0] + parts = re.match('.*@param\s*(\w*)\s*', line) + if parts and len(parts.groups()) == 1: + param = parts.groups()[0] + params.append(param) + parts = re.match('#define\s+(\w+)\s+(\w*)',line) + if parts and len(parts.groups()) == 2: + (key, value) = parts.groups() + if format != None: + if key.lower().startswith('hci_subevent_'): + le_events.append((value, key.lower().replace('hci_subevent_', 'hci_event_'), format, params)) + else: + events.append((value, key, format, params)) + event_types.add(key) + params = [] + format = None + return (events, le_events, event_types) + +# # read defines from hci_cmds.h and hci.h +read_defines(hci_cmds_h_path) +read_defines(hci_h_path) + +# # parse commands and generate BTstack.java +parse_commands(hci_cmds_c_path) + +# parse hci.h to get used events +(events, le_events, event_types) = parse_events(hci_cmds_h_path) + +# create events, le meta events, and event factory +create_events(events) +create_events(le_events) +create_event_factory(events, le_events, event_types) + +# done +print 'Done!' diff --git a/java/src/com/bluekitchen/btstack/BD_ADDR.java b/java/src/com/bluekitchen/btstack/BD_ADDR.java new file mode 100644 index 000000000..60d899317 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/BD_ADDR.java @@ -0,0 +1,42 @@ +package com.bluekitchen.btstack; + +public class BD_ADDR { + + public static final int LEN = 6; + + byte address[] = new byte[LEN]; + + public byte[] getBytes(){ + return address; + } + + public BD_ADDR(String text){ + String[] parts = text.split(":"); + if (parts.length != LEN) return; + for (int i = 0; i < LEN ; i++){ + Util.storeByte(address, LEN - 1 - i, Integer.parseInt(parts[i], 16)); + } + } + + public BD_ADDR(byte address[]){ + if (address.length != LEN) return; + System.arraycopy(address, 0, this.address, 0, LEN); + } + + public String toString(){ + StringBuffer buffer = new StringBuffer(); + for (int i = LEN - 1 ; i >= 0 ; i--){ + if (i != LEN - 1){ + buffer.append(":"); + } + buffer.append(String.format("%02X", Util.readByte(address, i))); + } + return buffer.toString(); + } + + public static void main(String args[]){ + BD_ADDR addr = new BD_ADDR("11:22:33:44:55:66"); + Util.hexdump(addr.getBytes()); + System.out.println( addr.toString()); + } +} diff --git a/java/src/com/bluekitchen/btstack/BT_UUID.java b/java/src/com/bluekitchen/btstack/BT_UUID.java new file mode 100644 index 000000000..827c09521 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/BT_UUID.java @@ -0,0 +1,86 @@ +package com.bluekitchen.btstack; + +import java.util.Arrays; + +// UUID stored in big endian format +// TODO store UUID internally as LE + +public class BT_UUID { + + private static final byte BluetoothBaseUUID[] = { 0x00, 0x00, 0x00, 0x00, /* - */ 0x00, 0x00, /* - */ 0x10, 0x00, /* - */ + (byte)0x80, 0x00, /* - */ 0x00, (byte)0x80, 0x5F, (byte)0x9B, 0x34, (byte)0xFB }; + + byte [] uuid = new byte[16]; + + public BT_UUID(long uuid32){ + System.arraycopy(BluetoothBaseUUID, 0, uuid, 0, 16); + Util.storeNet32(uuid, 0, uuid32); + } + + public BT_UUID(byte[] uuid128LE){ + Util.flipX(uuid128LE, uuid); + } + + public BT_UUID(String uuidString){ + String parts[] = uuidString.split("-"); + if (parts.length != 5) return; + Util.storeNet32(uuid, 0, Long.parseLong(parts[0], 16)); + Util.storeNet32(uuid, 4, Long.parseLong(parts[1]+parts[2], 16)); + Util.storeNet32(uuid, 8, Long.parseLong((parts[3]+parts[4]).substring(0, 8), 16)); + Util.storeNet32(uuid, 12, Long.parseLong((parts[3]+parts[4]).substring(8, 16), 16)); + } + + public String toString(){ + return String.format("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); + } + + public long getUUID32(){ + for (int i = 4; i < 16 ; i++){ + if (uuid[i] != BluetoothBaseUUID[i]) return 0; + } + return Util.readNet32(uuid, 0); + } + + public byte[] getBytes(){ + byte result[] = new byte[16]; + Util.flipX(uuid, result); + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(uuid); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof BT_UUID)) { + return false; + } + BT_UUID other = (BT_UUID) obj; + if (!Arrays.equals(uuid, other.uuid)) { + return false; + } + return true; + } + + public static void main (String arg[]){ + BT_UUID uuid = new BT_UUID(0x2800); + System.out.println("0x2800 = " + uuid); + System.out.println("32bit UUID = " + uuid.getUUID32()); + String uuidString = uuid.toString(); + BT_UUID uuid2 = new BT_UUID(uuidString); + System.out.println("equal " + uuid.equals(uuid2)); + } +} diff --git a/java/src/com/bluekitchen/btstack/BTstackClient.java b/java/src/com/bluekitchen/btstack/BTstackClient.java new file mode 100644 index 000000000..089883a6e --- /dev/null +++ b/java/src/com/bluekitchen/btstack/BTstackClient.java @@ -0,0 +1,99 @@ +package com.bluekitchen.btstack; + +public class BTstackClient { + + /** + * BTstack Server Client + * uses background receive thread + */ + + public static final int DEFAULT_TCP_PORT = 13333; + public static final String DEFAULT_UNIX_SOCKET = "/tmp/BTstack"; + + private volatile SocketConnection socketConnection; + private PacketHandler packetHandler; + private boolean connected; + private Thread rxThread; + private String unixDomainSocketPath; + private int tcpPort; + + public BTstackClient(){ + connected = false; + socketConnection = null; + rxThread = null; + } + + public void setUnixDomainSocketPath(String path){ + this.unixDomainSocketPath = path; + } + + public void setTcpPort(int port){ + this.tcpPort = port; + } + + public void registerPacketHandler(PacketHandler packetHandler){ + this.packetHandler = packetHandler; + } + + public boolean connect(){ + + if (tcpPort == 0){ + try { + Class clazz = Class.forName("com.bluekitchen.btstack.SocketConnectionUnix"); + socketConnection = (SocketConnection) clazz.newInstance(); + if (unixDomainSocketPath != null){ + socketConnection.setUnixDomainSocketPath(unixDomainSocketPath); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return false; + } catch (InstantiationException e) { + e.printStackTrace(); + return false; + } catch (IllegalAccessException e) { + e.printStackTrace(); + return false; + } + + } else { + // TODO implement SocketConnectionTcp + socketConnection = new SocketConnectionTCP(); + socketConnection.setTcpPort(tcpPort); + } + + connected = socketConnection.connect(); + if (!connected) return false; + + rxThread = new Thread(new Runnable(){ + @Override + public void run() { + while (socketConnection != null && !Thread.currentThread().isInterrupted()){ + Packet packet = socketConnection.receivePacket(); + if (packet == null) break; + if (packet.getPacketType() == Packet.HCI_EVENT_PACKET){ + packetHandler.handlePacket(EventFactory.eventForPacket(packet)); + continue; + } + packetHandler.handlePacket(packet); + } + System.out.println("Rx Thread: Disconnected"); + } + }); + rxThread.start(); + + return true; + } + + public boolean sendPacket(Packet packet){ + if (socketConnection == null) return false; + return socketConnection.sendPacket(packet); + } + + public void disconnect(){ + if (socketConnection == null) return; + if (rxThread == null) return; + rxThread.interrupt(); + socketConnection.disconnect(); + socketConnection = null; + } +} \ No newline at end of file diff --git a/java/src/com/bluekitchen/btstack/Event.java b/java/src/com/bluekitchen/btstack/Event.java new file mode 100644 index 000000000..e57f1ce99 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/Event.java @@ -0,0 +1,23 @@ +package com.bluekitchen.btstack; + +public class Event extends Packet { + + public Event(byte data[], int payloadLen){ + super(HCI_EVENT_PACKET, 0, data, payloadLen); + } + + public Event(Packet packet){ + super(HCI_EVENT_PACKET, 0, packet.getBuffer(), packet.getPayloadLen()); + } + + public int getEventType(){ + return Util.readByte(data, 0); + } + + public String toString(){ + StringBuffer t = new StringBuffer(); + t.append(String.format("Event type %d, len %d: ", getEventType(), getPayloadLen())); + t.append(Util.asHexdump(data, payloadLen)); + return t.toString(); + } +} diff --git a/java/src/com/bluekitchen/btstack/GATTCharacteristic.java b/java/src/com/bluekitchen/btstack/GATTCharacteristic.java new file mode 100644 index 000000000..c9e3b7c69 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/GATTCharacteristic.java @@ -0,0 +1,55 @@ +package com.bluekitchen.btstack; + +import java.util.Arrays; + +public class GATTCharacteristic { + + /** + uint16_t start_handle; + uint16_t value_handle; + uint16_t end_handle; + uint16_t properties; + uint8_t uuid128[16]; + */ + + public static final int LEN = 24; + + byte data[] = new byte[LEN]; + + public byte[] getBytes(){ + return data; + } + + public GATTCharacteristic(byte address[]){ + if (address.length != LEN) return; + System.arraycopy(address, 0, this.data, 0, LEN); + } + + public int getStartHandle(){ + return Util.readBt16(data, 0); + } + + public int getValueHandle(){ + return Util.readBt16(data, 2); + } + + public int getEndHandle(){ + return Util.readBt16(data, 4); + } + + public int getProperties(){ + return Util.readBt16(data, 6); + } + + public BT_UUID getUUID(){ + return new BT_UUID(Arrays.copyOfRange(data, 8, 8 + 16)); + } + + @Override + public String toString() { + return "GattCharacteristic [getStartHandle()=" + getStartHandle() + + ", getValueHandle()=" + getValueHandle() + + ", getEndHandle()=" + getEndHandle() + ", getProperties()=" + + getProperties() + ", getUUID()=" + getUUID() + "]"; + } +} diff --git a/java/src/com/bluekitchen/btstack/GATTCharacteristicDescriptor.java b/java/src/com/bluekitchen/btstack/GATTCharacteristicDescriptor.java new file mode 100644 index 000000000..ba7f54fed --- /dev/null +++ b/java/src/com/bluekitchen/btstack/GATTCharacteristicDescriptor.java @@ -0,0 +1,38 @@ +package com.bluekitchen.btstack; + +import java.util.Arrays; + +public class GATTCharacteristicDescriptor { + + /** + uint16_t handle; + uint8_t uuid128[16]; + */ + + public static final int LEN = 18; + + byte data[] = new byte[LEN]; + + public byte[] getBytes(){ + return data; + } + + public GATTCharacteristicDescriptor(byte address[]){ + if (address.length != LEN) return; + System.arraycopy(address, 0, this.data, 0, LEN); + } + + public int getHandle(){ + return Util.readBt16(data, 0); + } + + public BT_UUID getUUID(){ + return new BT_UUID(Arrays.copyOfRange(data, 2, 2 + 16)); + } + + @Override + public String toString() { + return "GattCharacteristicDescriptor [getHandle()=" + getHandle() + + ", getUUID()=" + getUUID() + "]"; + } +} diff --git a/java/src/com/bluekitchen/btstack/GATTService.java b/java/src/com/bluekitchen/btstack/GATTService.java new file mode 100644 index 000000000..2479f03a5 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/GATTService.java @@ -0,0 +1,44 @@ +package com.bluekitchen.btstack; + +import java.util.Arrays; + +public class GATTService { + + /** + uint16_t start_group_handle; + uint16_t end_group_handle; + uint8_t uuid128[16]; + */ + + public static final int LEN = 20; + + byte data[] = new byte[LEN]; + + public byte[] getBytes(){ + return data; + } + + public GATTService(byte data[]){ + if (data.length != LEN) return; + System.arraycopy(data, 0, this.data, 0, LEN); + } + + public int getStartGroupHandle(){ + return Util.readBt16(data, 0); + } + + public int getEndGroupHandle(){ + return Util.readBt16(data, 2); + } + + public BT_UUID getUUID(){ + return new BT_UUID(Arrays.copyOfRange(data, 4, 4 + 16)); + } + + @Override + public String toString() { + return "GattService [getStartGroupHandle()=" + getStartGroupHandle() + + ", getEndGroupHandle()=" + getEndGroupHandle() + + ", getUUID()=" + getUUID() + "]"; + } +} diff --git a/java/src/com/bluekitchen/btstack/Packet.java b/java/src/com/bluekitchen/btstack/Packet.java new file mode 100644 index 000000000..f809ea356 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/Packet.java @@ -0,0 +1,48 @@ +package com.bluekitchen.btstack; + +public class Packet { + + public static final int HCI_COMMAND_PACKET = 1; + public static final int HCI_EVENT_PACKET = 4; + + protected byte[] data; + protected int payloadLen; + protected int packetType; + protected int channel; + + public int getPacketType() { + return packetType; + } + + public byte[] getBuffer() { + return data; + } + + public int getPayloadLen(){ + return payloadLen; + } + + public int getChannel() { + return channel; + } + + public Packet(int packetType, int channel, byte[] buffer, int payloadLen){ + this.packetType = packetType; + this.channel = channel; + this.data = new byte[payloadLen]; + System.arraycopy(buffer, 0, this.data, 0, payloadLen); + this.data = buffer; + this.payloadLen = payloadLen; + } + + public String toString(){ + StringBuffer t = new StringBuffer(); + t.append(String.format("Packet %d, channel %d, len %d: ", packetType, channel, payloadLen)); + t.append(Util.asHexdump(data, payloadLen)); + return t.toString(); + } + + public void dump(){ + System.out.println(this.toString()); + } +} diff --git a/java/src/com/bluekitchen/btstack/PacketHandler.java b/java/src/com/bluekitchen/btstack/PacketHandler.java new file mode 100644 index 000000000..139037f05 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/PacketHandler.java @@ -0,0 +1,5 @@ +package com.bluekitchen.btstack; + +public interface PacketHandler { + void handlePacket(Packet packet); +} diff --git a/java/src/com/bluekitchen/btstack/SocketConnection.java b/java/src/com/bluekitchen/btstack/SocketConnection.java new file mode 100644 index 000000000..0bf7bf6ef --- /dev/null +++ b/java/src/com/bluekitchen/btstack/SocketConnection.java @@ -0,0 +1,18 @@ +package com.bluekitchen.btstack; + +public abstract class SocketConnection { + + public abstract boolean connect(); + + public abstract boolean sendPacket(Packet packet); + + public abstract Packet receivePacket(); + + public abstract void disconnect(); + + public void setUnixDomainSocketPath(String path) { + } + + public void setTcpPort(int port) { + } +} \ No newline at end of file diff --git a/java/src/com/bluekitchen/btstack/SocketConnectionTCP.java b/java/src/com/bluekitchen/btstack/SocketConnectionTCP.java new file mode 100644 index 000000000..a413cced8 --- /dev/null +++ b/java/src/com/bluekitchen/btstack/SocketConnectionTCP.java @@ -0,0 +1,104 @@ +package com.bluekitchen.btstack; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + + +public class SocketConnectionTCP extends SocketConnection { + + private int port; + private Socket socket; + private InputStream in; + private OutputStream out; + private byte inHeader[] = new byte[6]; + private byte inPayload[] = new byte[2000]; + + public SocketConnectionTCP(){ + socket = null; + } + + public void setTcpPort(int port) { + this.port = port; + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#connect() + */ + @Override + public boolean connect() { + try { + socket = new Socket("localhost", port); + in = socket.getInputStream(); + out = socket.getOutputStream(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#sendPacket(com.bluekitchen.btstack.Packet) + */ + @Override + public boolean sendPacket(Packet packet) { + + if (out == null) return false; + + try { + System.out.println("Send "); Util.hexdump(packet.getBuffer(), packet.getPayloadLen()); + out.write(headerForPacket(packet)); + out.write(packet.getBuffer()); + out.flush(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#receivePacket() + */ + @Override + public Packet receivePacket() { + + if (in == null) return null; + + int bytes_read = Util.readExactly(in, inHeader, 0, 6); + if (bytes_read != 6) return null; + + int packetType = Util.readBt16(inHeader, 0); + int channel = Util.readBt16(inHeader, 2); + int len = Util.readBt16(inHeader, 4); + + Util.readExactly(in, inPayload, 0, len); + + Packet packet = new Packet(packetType, channel ,inPayload, len); + return packet; + } + + /* (non-Javadoc) + * @see com.bluekitchen.btstack.SocketConnection#disconnect() + */ + @Override + public void disconnect() { + + if (socket != null){ + try { + socket.close(); + } catch (IOException e) { + } + } + } + + private byte[] headerForPacket(Packet packet) { + byte header[] = new byte[6]; + Util.storeBt16(header, 0, packet.getPacketType()); + Util.storeBt16(header, 2, packet.getChannel()); + Util.storeBt16(header, 4, packet.getBuffer().length); + return header; + } +} diff --git a/java/src/com/bluekitchen/btstack/Util.java b/java/src/com/bluekitchen/btstack/Util.java new file mode 100644 index 000000000..06119f50d --- /dev/null +++ b/java/src/com/bluekitchen/btstack/Util.java @@ -0,0 +1,146 @@ +package com.bluekitchen.btstack; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +public class Util { + public static int opcode(int ogf, int ocf){ + return ocf | (ogf << 10); + } + + public static int readByte(byte[] buffer, int offset){ + int data = buffer[offset]; + if (data >= 0) return data; + return data + 256; + } + + public static int readBt16(byte[] buffer, int offset){ + return readByte(buffer, offset) | (readByte(buffer, offset + 1) << 8); + } + + public static int readBt24(byte[] buffer, int offset){ + return readByte(buffer, offset) | (readByte(buffer, offset + 1) << 8) | (readByte(buffer, offset + 2) << 16) ; + } + + public static long readBt32(byte[] buffer, int offset){ + return (((long) readByte(buffer, offset+3)) << 24) | readByte(buffer, offset) | (readByte(buffer, offset + 1) << 8) | (readByte(buffer, offset + 2) << 16) ; + } + + public static BD_ADDR readBdAddr(byte[] buffer, int offset){ + return new BD_ADDR(Arrays.copyOfRange(buffer, offset, offset + BD_ADDR.LEN)); + } + + public static GATTService readGattService(byte[] buffer, int offset){ + return new GATTService(Arrays.copyOfRange(buffer, offset, offset + GATTService.LEN)); + } + + public static GATTCharacteristic readGattCharacteristic(byte[] buffer, int offset){ + return new GATTCharacteristic(Arrays.copyOfRange(buffer, offset, offset + GATTCharacteristic.LEN)); + } + + public static GATTCharacteristicDescriptor readGattCharacteristicDescriptor(byte[] buffer, int offset){ + return new GATTCharacteristicDescriptor(Arrays.copyOfRange(buffer, offset, offset + GATTCharacteristicDescriptor.LEN)); + } + + public static int readExactly(InputStream in, byte[] buffer, int offset, int len){ + int readTotal = 0; + try { + while (len > 0){ + int read; + read = in.read(buffer, offset, len); + if (read < 0) break; + len -= read; + offset += read; + readTotal += read; + } + } catch (IOException e) { + } + return readTotal; + } + + public static void storeByte(byte[] buffer, int offset, int value){ + buffer[offset] = (byte) value; + } + + public static void storeBt16(byte[] buffer, int offset, int value){ + storeByte(buffer, offset, value & 0xff); + storeByte(buffer, offset+1, value >> 8); + } + + public static void storeBt24(byte[] buffer, int offset, int value){ + storeByte(buffer, offset, value & 0xff); + storeByte(buffer, offset+1, value >> 8); + storeByte(buffer, offset+2, value >> 16); + } + + public static void storeBt32(byte[] buffer, int offset, long value){ + storeByte(buffer, offset, (int) (value & 0xff)); + storeByte(buffer, offset+1, (int) (value >> 8)); + storeByte(buffer, offset+2, (int) (value >> 16)); + storeByte(buffer, offset+3, (int) (value >> 24)); + } + + public static void storeBytes(byte[] buffer, int offset, byte[] value) { + System.arraycopy(value, 0, buffer, offset, value.length); + } + + public static void storeBytes(byte[] buffer, int offset, byte[] value, int len) { + int bytes_to_copy = Math.min(value.length, len); + System.arraycopy(value, 0, buffer, offset, bytes_to_copy); + for (int i = bytes_to_copy; i < len ; i++){ + buffer[i] = 0; + } + } + + public static void storeString(byte[] buffer, int offset, String value, int len) { + byte data[] = new byte[0]; + try { + data = value.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + storeBytes(buffer, offset, data, len); + } + + public static String asHexdump(byte[] buffer, int len){ + StringBuffer t = new StringBuffer(); + for (int i = 0; i < len ; i++){ + t.append(String.format("0x%02x, ", readByte(buffer, i))); + } + return t.toString(); + } + + public static String asHexdump(byte[] buffer){ + return asHexdump(buffer, buffer.length); + } + + public static void hexdump(byte[] buffer, int len){ + System.out.println(asHexdump(buffer, len)); + System.out.println(); + } + + public static void hexdump(byte[] buffer){ + hexdump(buffer, buffer.length); + } + + public static void flipX(byte src[], byte dst[]){ + int len = src.length; + if (len != dst.length) return; + for (int i = 0; i < len ; i++) { + dst[len - 1 - i] = src[i]; + } + } + + public static void storeNet32(byte[] buffer, int offset, long value) { + storeByte(buffer, offset+3, (int) (value & 0xff)); + storeByte(buffer, offset+2, (int) (value >> 8)); + storeByte(buffer, offset+1, (int) (value >> 16)); + storeByte(buffer, offset, (int) (value >> 24)); + } + + public static long readNet32(byte[] buffer, int offset) { + return (((long) readByte(buffer, offset)) << 24) | (readByte(buffer, offset + 1) << 16) | (readByte(buffer, offset + 2) << 8) | readByte(buffer, offset + 3); + } +}