디바이스와 modbus통신을 위해서 소캣통신을 구현해야 했다.
구글링해서 github에서 구한 소스를 조금 고쳐서 사용하는 것으로 해결 했다.
먼저 SerialSocket.java
package com...;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.Executors;
public class SerialSocket implements Runnable {
private static final UUID BLUETOOTH_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private final BroadcastReceiver disconnectBroadcastReceiver;
private Context context;
private SerialListener listener;
private BluetoothDevice device;
private BluetoothSocket socket;
private boolean connected;
public SerialSocket(Context context, BluetoothDevice device) {
if(context instanceof Activity)
throw new InvalidParameterException("expected non UI context");
this.context = context;
this.device = device;
disconnectBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(listener != null)
listener.onSerialIoError(new IOException("background disconnect"));
disconnect();
}
};
}
String getName() {
return device.getName() != null ? device.getName() : device.getAddress();
}
public void connect(SerialListener listener) throws IOException {
this.listener = listener;
context.registerReceiver(disconnectBroadcastReceiver, new IntentFilter(Constants.INTENT_ACTION_DISCONNECT));
Executors.newSingleThreadExecutor().submit(this);
}
public void disconnect() {
listener = null;
if(socket != null) {
try {
socket.close();
} catch (Exception ignored) {
}
socket = null;
}
try {
context.unregisterReceiver(disconnectBroadcastReceiver);
} catch (Exception ignored) {
}
}
public void write(byte[] data) throws IOException {
if (!connected)
throw new IOException("not connected");
socket.getOutputStream().write(data);
}
@Override
public void run() {
try {
socket = device.createRfcommSocketToServiceRecord(BLUETOOTH_SPP);
socket.connect();
if(listener != null)
listener.onSerialConnect();
} catch (Exception e) {
if(listener != null)
listener.onSerialConnectError(e);
try {
socket.close();
} catch (Exception ignored) {
}
socket = null;
return;
}
connected = true;
try {
byte[] buffer = new byte[1024];
int len;
while (true) {
len = socket.getInputStream().read(buffer);
byte[] data = Arrays.copyOf(buffer, len);
if(listener != null)
listener.onSerialRead(data);
}
} catch (Exception e) {
connected = false;
if (listener != null)
listener.onSerialIoError(e);
try {
socket.close();
} catch (Exception ignored) {
}
socket = null;
}
}
}
SerialService.java
package com...;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
public class SerialService extends Service implements SerialListener {
public class SerialBinder extends Binder {
public SerialService getService(){
return SerialService.this;
}
}
private enum QueueType {Connect, ConnectError, Read, IoError}
private class QueueItem {
QueueType type;
byte[] data;
Exception e;
QueueItem(QueueType type, byte[] data, Exception e) { this.type=type; this.data=data; this.e=e; }
}
private final Handler mainLooper;
private final IBinder binder;
private final Queue<QueueItem> queue1, queue2;
private SerialSocket socket;
private SerialListener listener;
private boolean connected;
public SerialService() {
mainLooper = new Handler(Looper.getMainLooper());
binder = new SerialBinder();
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
@Override
public void onDestroy() {
cancelNotification();
disconnect();
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void connect(SerialSocket socket) throws IOException {
socket.connect(this);
this.socket = socket;
connected = true;
}
public void disconnect() {
connected = false;
cancelNotification();
if(socket != null) {
socket.disconnect();
socket = null;
}
}
public void write(byte[] data) throws IOException {
if(!connected)
throw new IOException("not connected");
socket.write(data);
}
public void attach(SerialListener listener) {
if(Looper.getMainLooper().getThread() != Thread.currentThread())
throw new IllegalArgumentException("not in main thread");
cancelNotification();
synchronized (this) {
this.listener = listener;
}
for(QueueItem item : queue1) {
switch(item.type) {
case Connect: listener.onSerialConnect (); break;
case ConnectError: listener.onSerialConnectError (item.e); break;
case Read: listener.onSerialRead (item.data); break;
case IoError: listener.onSerialIoError (item.e); break;
}
}
for(QueueItem item : queue2) {
switch(item.type) {
case Connect: listener.onSerialConnect (); break;
case ConnectError: listener.onSerialConnectError (item.e); break;
case Read: listener.onSerialRead (item.data); break;
case IoError: listener.onSerialIoError (item.e); break;
}
}
queue1.clear();
queue2.clear();
}
public void detach() {
if(connected)
createNotification();
listener = null;
}
private void createNotification() {
}
private void cancelNotification() {
stopForeground(true);
}
public void onSerialConnect() {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialConnect();
} else {
queue1.add(new QueueItem(QueueType.Connect, null, null));
}
});
} else {
queue2.add(new QueueItem(QueueType.Connect, null, null));
}
}
}
}
public void onSerialConnectError(Exception e) {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialConnectError(e);
} else {
queue1.add(new QueueItem(QueueType.ConnectError, null, e));
cancelNotification();
disconnect();
}
});
} else {
queue2.add(new QueueItem(QueueType.ConnectError, null, e));
cancelNotification();
disconnect();
}
}
}
}
public void onSerialRead(byte[] data) {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialRead(data);
} else {
queue1.add(new QueueItem(QueueType.Read, data, null));
}
});
} else {
queue2.add(new QueueItem(QueueType.Read, data, null));
}
}
}
}
public void onSerialIoError(Exception e) {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialIoError(e);
} else {
queue1.add(new QueueItem(QueueType.IoError, null, e));
cancelNotification();
disconnect();
}
});
} else {
queue2.add(new QueueItem(QueueType.IoError, null, e));
cancelNotification();
disconnect();
}
}
}
}
}
SerialListener.java
package com....;
public interface SerialListener {
void onSerialConnect ();
void onSerialConnectError (Exception e);
void onSerialRead (byte[] data);
void onSerialIoError (Exception e);
}
마지막으로 실제 사용 예시
package com...
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com...SerialListener
import com...SerialService
import com...SerialSocket
import com...CRC16Modbus
import com...TextUtil
const val STATE_SETTING = 1
const val STATE_MEASURE = 2
const val STATE_SET_VALUE = 3
class DataRelayActivityV2 : AppCompatActivity(), ServiceConnection, SerialListener {
enum class Connected { False, Pending, True }
lateinit var device: BluetoothDevice
lateinit var deviceAddress: String
private var service: SerialService? = null
private var connected: Connected = Connected.False
var initialStart: Boolean = true
private val newline = TextUtil.newline_crlf
lateinit var btnSetting: Button
lateinit var btnMeasure: Button
lateinit var btnSendSetting: Button
val requestSetting: String = "0103000000384418"
val requestMeasure: String = "010400000014F005"
var requestSetVaule: String = "0106"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_data_relay_v2)
device = intent.getParcelableExtra("device")!!
deviceAddress = intent.getStringExtra("deviceAddress").toString()
init()
}
private fun init() {
btnSetting = findViewById(R.id.btn_setting)
btnSetting.setOnClickListener {
send(requestSetting)
}
btnMeasure = findViewById(R.id.btn_measure)
btnMeasure.setOnClickListener {
send(requestMeasure)
}
btnSendSetting = findViewById(R.id.btn_send_setting)
btnSendSetting.setOnClickListener {
val crcCRC16Modbus: CRC16Modbus = CRC16Modbus()
var cmd: String = "010600000002"
var nCmd: IntArray = intArrayOf(0x01, 0x06, 0x00, 0x00, 0x00, 0x02)
var sCmd: String = ""
for (i in (nCmd.indices)) {
sCmd += String.format("%02X", nCmd[i].toByte())
}
Log.d("TEST", "sCmd = $sCmd")
var crc: ByteArray = crcCRC16Modbus.getCRC16Modbus(nCmd)
sCmd += String.format("%02X", crc[0])
sCmd += String.format("%02X", crc[1])
Log.d("TEST", "after sCmd = $sCmd")
Log.d("TEST", "toHexString = " + TextUtil.toHexString(crc))
}
}
private fun connect() {
Log.d("TEST", "connect called !!!")
try {
val btAdapter: BluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val device: BluetoothDevice = btAdapter.getRemoteDevice(deviceAddress)
Log.d("TEST", "device address = $deviceAddress")
showToast("Connecting....")
connected = Connected.Pending
val socket = SerialSocket(this.applicationContext, device)
service?.connect(socket)
} catch (e: Exception) {
onSerialConnectError(e)
}
}
private fun disconnect() {
connected = Connected.False
service?.disconnect()
}
private fun send(str: String) {
var data: ByteArray
var msg: String
if (connected == Connected.True) {
var sb: StringBuilder = StringBuilder()
TextUtil.toHexString(sb, TextUtil.fromHexString(str))
msg = sb.toString()
data = TextUtil.fromHexString(msg)
Log.d("TEST", "send data = $msg")
service?.write(data)
} else {
showToast("Not Connected")
}
}
override fun onDestroy() {
if (connected != Connected.False)
disconnect()
this.stopService(Intent(this, SerialService::class.java))
super.onDestroy()
}
override fun onStart() {
super.onStart()
Log.d("TEST", "onStart Called !!!")
if (service != null) {
Log.d("TEST", "on start service is not null !!!")
service?.attach(this)
} else {
Log.d("TEST", "on start service is null !!!")
Intent(this, SerialService::class.java).also { intent ->
bindService(intent, this, Context.BIND_AUTO_CREATE)
}
}
}
override fun onStop() {
if (service != null && !this.isChangingConfigurations) {
service!!.detach()
}
super.onStop()
}
private fun receive(data: ByteArray): String {
Log.d("TEST", "received called !!!")
Log.d("TEST", "data = " + TextUtil.toHexString(data))
return TextUtil.toHexString(data)
}
override fun onResume() {
super.onResume()
if (initialStart && service != null) {
initialStart = false
runOnUiThread { connect() }
}
}
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
Log.d("TEST", "on Service Connected called !!!")
val binder = p1 as SerialService.SerialBinder
service = binder.service
service?.attach(this)
if (initialStart) {
initialStart = false;
runOnUiThread {
connect()
}
}
}
override fun onServiceDisconnected(p0: ComponentName?) {
}
override fun onSerialConnect() {
showToast("connected")
connected = Connected.True
}
override fun onSerialConnectError(e: java.lang.Exception?) {
if (e != null) {
showToast("Connection failed! " + e.message.toString())
e.printStackTrace()
}
}
override fun onSerialRead(data: ByteArray?) {
Log.d("TEST", "on Serial Read called !!!")
if (data != null) {
receive(data)
}
}
override fun onSerialIoError(e: java.lang.Exception?) {
if (e != null) {
e.printStackTrace()
showToast("Connection lost! " + e.message)
}
}
private fun showToast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}
어쩌다가 역대급 긴 글이 된듯...