import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.zip.CRC32;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothGattDescriptor;
//Function call by B4A part
public void ConnectionHexisViaMAC(String mac,Context context)
{
BluetoothHexis bluetoothHexis;
bluetoothHexis = new BluetoothHexis();
bluetoothHexis.connect(mac);
}
//Hexis device class
public class BluetoothHexis
{
// UUID service to connect
private final String SERVICE_UUID = "00005301-0000-0041-4c50-574953450000";
// Write characteristics
private final String CHARACTERISTIC_UUID_WRITE = "00005302-0000-0041-4c50-574953450000";
// Read characteristics
private final String CHARACTERISTIC_UUID_NOTIFY = "00005303-0000-0041-4c50-574953450000";
//START ACK
byte[] START_ACK =
{
(byte)0x05, (byte)0x06, (byte)0x40, (byte)0x00 ,(byte)0x08,
(byte)0x01 ,(byte)0x5B, (byte)0x95
};
//Standard Ack
byte[] STD_ACK =
{
(byte) 0x05, (byte) 0x01, (byte) 0x40, (byte) 0x01, (byte) 0x00,
(byte) 0x01, (byte) 0xb8, (byte) 0x55, (byte) 0x81, (byte) 0x09,
(byte) 0x40, (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x46,
(byte) 0xB0, (byte) 0x77, (byte) 0xE2, (byte) 0x00, (byte) 0x01,
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0xF0, (byte) 0x72
};
byte[] STD2_ACK =
{
(byte) 0x05, (byte) 0x01, (byte) 0x40, (byte) 0x02, (byte) 0x00,
(byte) 0x01, (byte) 0x48, (byte) 0x55
};
// End session
private final byte[] END_SESSION_PRE = {(byte)0x81, (byte) 0x02, (byte) 0x40};
// End session post
private final byte[] END_SESSION_POST1 = {(byte) 0x03, (byte)0x01};
private final byte[] END_SESSION_POST2 = {(byte) 0x77, (byte) 0xFF,(byte) 0x67,(byte) 0xB0};
// Block index
private int index = 0;
private BluetoothGatt gatt;
private BluetoothDevice device;
private boolean startReadBlock = false;
private int StartAckCount = 1;
public CompletableFuture<Void> connect(String deviceAddress)
{
CompletableFuture<Void> future = new CompletableFuture<>();
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
device = bluetoothAdapter.getRemoteDevice(deviceAddress);
gatt = device.connectGatt(null, false, new BluetoothGattCallback()
{
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
{
if (newState == BluetoothGatt.STATE_CONNECTED) {
BA.Log("Connected to GATT server.");
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
BA.Log("Disconnected from GATT server, status: " + status);
future.complete(null);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS)
{
BluetoothGattService service = gatt.getService(UUID.fromString(SERVICE_UUID));
if (service != null)
{
BluetoothGattCharacteristic characteristicNotify = service.getCharacteristic(UUID.fromString(CHARACTERISTIC_UUID_NOTIFY));
BluetoothGattCharacteristic characteristicWrite = service.getCharacteristic(UUID.fromString(CHARACTERISTIC_UUID_WRITE));
characteristicWrite.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
if (characteristicNotify != null)
{
startCommunication(gatt,characteristicNotify,characteristicWrite);
}else{
BA.Log("Notification characteristic not found.");
}
} else
{
BA.Log("Service not found.");
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)
{
byte[] data = characteristic.getValue();
BA.Log("Received HandleRX: " + bytesToHex(data));
if (startReadBlock)
{
readDataBlock(data);
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
BA.Log("Characteristic written successfully.");
} else {
BA.Log("Error writing characteristic: " + status);
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
BA.Log("onDescriptorWrite :" + ((status == BluetoothGatt.GATT_SUCCESS) ? "Sucess" : "false"));
}
});
return future;
}
// Costruzione del messaggio WhoAreYou con timestamp-------------------------------------
private byte[] BuildWhoYouAreMessage(BluetoothGattCharacteristic characteristicWrite)
{
LocalDateTime current = LocalDateTime.now();
byte cy = (byte) (current.getYear() % 100);
byte[] dateDmy = {(byte) current.getDayOfMonth(), (byte) current.getMonthValue(), cy};
byte[] dateHms = {(byte) current.getHour(), (byte) current.getMinute(), (byte) current.getSecond()};
byte[] preWho = {(byte) 0x81, (byte) 0x10, (byte) 0x40, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x0a, (byte) 0x42, (byte) 0x77, (byte) 0xe0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
byte[] postWho = {(byte) 0x00, (byte) 0x00, (byte)0x0c};
List<Byte> whoAreYouMsg = new ArrayList<>();
for (byte b : preWho ) whoAreYouMsg.add(b);
for (byte b : dateDmy) whoAreYouMsg.add(b);
for (byte b : dateHms) whoAreYouMsg.add(b);
for (byte b : postWho) whoAreYouMsg.add(b);
byte[] whoAreYouMsgArray = new byte[whoAreYouMsg.size()];
for (int i = 0; i < whoAreYouMsg.size(); i++) whoAreYouMsgArray[i] = whoAreYouMsg.get(i);
byte[] whoAreYouMsgCrc = appendCRC16(whoAreYouMsgArray);
whoAreYouMsgArray = concatenate(whoAreYouMsgArray, whoAreYouMsgCrc);
return whoAreYouMsgArray;
}
//-----------------------------------------------------------------------------------
private void startCommunication(BluetoothGatt bluetoothGatt,BluetoothGattCharacteristic characteristicNotify, BluetoothGattCharacteristic characteristicWrite)
{
startReadBlock = false; index = 0;
//Notifica a True
bluetoothGatt.setCharacteristicNotification(characteristicNotify, true);
BluetoothGattDescriptor descriptor = characteristicNotify.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
boolean descriptorsucc = bluetoothGatt.writeDescriptor(descriptor);
BA.Log("Descriptor write initiated. Success: " + descriptorsucc);
sleepThread(1000);
// Invio dei messaggi START (il primo sarà sempre ignorato dalla scheda di destinazione)
handler.postDelayed(new Runnable()
{
@Override
public void run()
{
if(descriptorsucc)
{
//start message
writeCharacteristicForStart(CHARACTERISTIC_UUID_WRITE, START_ACK);
//sleepThread(1000);
writeCharacteristicForStart(CHARACTERISTIC_UUID_WRITE, START_ACK);
//who you are message
List<byte[]> chunks = returnChunks(BuildWhoYouAreMessage(characteristicWrite), characteristicWrite);
for (byte[] chunk : chunks)
{
writeCharacteristic(CHARACTERISTIC_UUID_WRITE, chunk, "Inviato I Chunk del messaggio WhoAreYou: " + bytesToHex(chunk));
}
//sleepThread(1000);
//Invia ACK
List<byte[]> ackChunks = returnChunks(STD_ACK, characteristicWrite);
for (byte[] chunk : ackChunks) writeCharacteristic(CHARACTERISTIC_UUID_WRITE, chunk, "Inviato Ack " + bytesToHex(chunk));
//sleepThread(2000);
//Invia ACK 2
writeCharacteristic(CHARACTERISTIC_UUID_WRITE, STD2_ACK,"Inviato Leggi EVA-DTS " + bytesToHex(STD2_ACK));
index = 3;
startReadBlock = true;
//sleepThread(5000);
bluetoothGatt.setCharacteristicNotification(characteristicNotify, false);
BluetoothGattDescriptor descriptor = characteristicNotify.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
BA.Log("Descriptor write initiated. Success: " + bluetoothGatt.writeDescriptor(descriptor));
}
}
}, 1000);
}
private void readDataBlock(byte[] data)
{
BA.Log("ENTRO IN READ DATA BLOCK");
byte[] preack = {0x05, 0x01, 0x40};
byte[] postind = {0x00, 0x01};
byte[] ack;
BA.Log("Data[0] = " + data[0]);
if (data[0] == (byte) 0x81)
{
if (data[2] != 0x40) {
BA.Log("Last block...");
ack = new byte[preack.length + 1 + postind.length + 2];
System.arraycopy(preack, 0, ack, 0, preack.length);
ack[preack.length] = (byte) index;
System.arraycopy(postind, 0, ack, preack.length + 1, postind.length);
byte[] ackCrc = appendCRC16(Arrays.copyOf(ack, ack.length - 2));
System.arraycopy(ackCrc, 1, ack, ack.length - 2, 1);
ack[ack.length - 1] = ackCrc[0];
handler.postDelayed(new Runnable()
{
@Override
public void run()
{
writeCharacteristic(CHARACTERISTIC_UUID_WRITE, ack,"Sent: Respond with acknowledgement " + Arrays.toString(ack));
}
}, 1000);
//BA.Log("Sent: Respond with acknowledgement " + Arrays.toString(ack));
byte[] endSession = new byte[END_SESSION_PRE.length + 1 + END_SESSION_POST1.length + 2];
System.arraycopy(END_SESSION_PRE, 0, endSession, 0, END_SESSION_PRE.length);
endSession[END_SESSION_PRE.length] = (byte) index;
System.arraycopy(END_SESSION_POST1, 0, endSession, END_SESSION_PRE.length + 1, END_SESSION_POST1.length);
byte[] endSessionCrc = appendCRC16(Arrays.copyOf(endSession, endSession.length - 2));
System.arraycopy(endSessionCrc, 1, endSession, endSession.length - 2, 1);
endSession[endSession.length - 1] = endSessionCrc[0];
writeCharacteristic(CHARACTERISTIC_UUID_WRITE, endSession,"");
BA.Log("Sent: End block endSession " + Arrays.toString(endSession));
writeCharacteristic(CHARACTERISTIC_UUID_WRITE, END_SESSION_POST2,"");
BA.Log("Sent: End block endSession2 " + Arrays.toString(END_SESSION_POST2));
return;
}
BA.Log("Read EVA-DTS audit...");
ack = new byte[preack.length + 1 + postind.length + 2];
System.arraycopy(preack, 0, ack, 0, preack.length);
ack[preack.length] = (byte) index;
System.arraycopy(postind, 0, ack, preack.length + 1, postind.length);
byte[] ackCrc = appendCRC16(Arrays.copyOf(ack, ack.length - 2));
System.arraycopy(ackCrc, 1, ack, ack.length - 2, 1);
ack[ack.length - 1] = ackCrc[0];
writeCharacteristic(CHARACTERISTIC_UUID_WRITE, ack,"");
BA.Log("Sent: Respond with acknowledgement for block with sequence ID " + Arrays.toString(ack));
if (index < 255) {
index++;
} else {
index = 0;
}
BA.Log("Index " + index);
}
}
//FUNZIONI DI MANAGEMENT PER LO SCAMBIO BOTTA E RISPOSTA CON HEXIS-------------------------------------------------------------------------------------------
private byte[] crc16(byte[] message)
{
CRC32 crc = new CRC32();
crc.update(message);
long crcValue = crc.getValue();
return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) crcValue).array();
}
private byte[] appendCRC16(byte[] data)
{
CRC32 crc = new CRC32();
crc.update(data, 0, data.length);
long checksum = crc.getValue();
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt((int) checksum);
byte[] result = new byte[2];
result[0] = buffer.get(2);
result[1] = buffer.get(3);
return result;
}
private byte[] concatenate(byte[] first, byte[] second)
{
byte[] result = new byte[first.length + second.length];
System.arraycopy(first, 0, result, 0, first.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
private String bytesToHex(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
private void sleepThread(int milliseconds)
{
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
BA.Log("Interruzione del thread: " + e.getMessage());
}
}
private List<byte[]> returnChunks(byte[] data, BluetoothGattCharacteristic characteristic)
{
int chunkSize = characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT ? 20 : 512;
List<byte[]> chunks = new ArrayList<>();
for (int i = 0; i < data.length; i += chunkSize) {
chunks.add(Arrays.copyOfRange(data, i, Math.min(data.length, i + chunkSize)));
}
return chunks;
}
private void writeCharacteristic(String uuid, byte[] data, String msg)
{
BluetoothGattService service = gatt.getService(UUID.fromString(SERVICE_UUID));
StartAckCount++;
if (service != null) {
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(uuid));
if (characteristic != null)
{
characteristic.setValue(data);
boolean success = gatt.writeCharacteristic(characteristic);
//BA.Log("Writing to characteristic " + uuid + ": " + bytesToHex(data) + ", success: " + success);
if(success) BA.Log(msg);
} else {
BA.Log("Characteristic not found: " + uuid);
}
} else {
BA.Log("Service not found: " + SERVICE_UUID);
}
}
private void writeCharacteristicForStart(String uuid, byte[] data)
{
BluetoothGattService service = gatt.getService(UUID.fromString(SERVICE_UUID));
if (service != null)
{
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(uuid));
if (characteristic != null) {
characteristic.setValue(data);
boolean success = gatt.writeCharacteristic(characteristic);
if(success)
{
BA.Log("Inviato " + StartAckCount + " START (se 1 Verrà Ignorato False) " + bytesToHex(START_ACK));
}else{
StartAckCount++;
}
} else {
BA.Log("Characteristic not found: " + uuid);
}
} else {
BA.Log("Service not found: " + SERVICE_UUID);
}
}
}