I have set up RabbitMQ, enabled web UI for management, enabled mqtt_plugin and the ports 1883, 8883, 5672, 15672 (Docker). I used Paho MqttClient for Android app I am developing to publish a message to the MQ broker. The connection is fine however, there is no message received as a check on the web UI and CLI.
Connection Page:
Channel Page:
Exchange Page:
Queues Page:
Below is the code I'm working on.
private static final String CONNECTION_URL = "tcp://my-app.com:1883";
private static final String USERNAME = "test_user";
private static final String PASSWORD = "test_pass";
private static final String EXCHANGE = "TestExchange";
private static final String QUEUE = "TestQueue";
private static final String TOPIC = "TestTopic";
// executed onCreate
private void initializeMQ() {
Log.d(TAG, "==== STARTING MQTT CONNECTION ====");
String clientId = "Skwamiyou";
client = new MqttAndroidClient(this, CONNECTION_URL, clientId);
MqttConnectOptions options = setConnectionOptions(USERNAME, PASSWORD);
try {
IMqttToken token = client.connect(options);
token.setActionCallback(new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.d(TAG, "Connected");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.d(TAG, "Failed connection");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private static MqttConnectOptions setConnectionOptions(String username, String password) {
MqttConnectOptions options = new MqttConnectOptions();
options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1);
options.setCleanSession(false);
options.setAutomaticReconnect(true);
options.setUserName(username);
options.setPassword(password.toCharArray());
return options;
}
// this is called on button click publish
public void publishLog() {
Log.d(TAG, "Publishing....");
counter++;
String payload = "Send to My MQ! - " + counter;
try {
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(1);
message.setRetained(true);
client.publish(TOPIC, message);
Toast.makeText(this, "MESSAGE SENT! - " + counter, Toast.LENGTH_SHORT).show();
} catch (MqttException e) {
e.printStackTrace();
}
}
I've been looking around for answers and tried reinstalling MQ but still got the same.
Here, is common extention for coonectMq and getting message from it. (MqConnectionExtention.kt)
fun Context.connectMq(publishTopicChannelName: String, onConnectionSuccess: (topic: String?, message: MqttMessage?) -> Unit) {
val mClientId = BuildConfig.CLIENT_ID + System.currentTimeMillis()
val mqttAndroidClient = MqttAndroidClient(this, "tcp://34.212.00.188:1883", mClientId)
Timber.e("ChannelName:$publishTopicChannelName")
mqttAndroidClient.setCallback(object : MqttCallbackExtended {
override fun connectComplete(reconnect: Boolean, serverURI: String) {
if (reconnect) { //addToHistory("Reconnected to : " + serverURI)
Log.e("TAG", "Reconnected to : $serverURI")
// Because Clean Session is true, we need to re-subscribe
try {
mqttAndroidClient.subscribe(publishTopicChannelName, 0, object : IMqttMessageListener {
override fun messageArrived(topic: String?, message: MqttMessage?) {
onConnectionSuccess(topic, message)
}
})
} catch (ex: MqttException) {
System.err.println("Exception whilst subscribing")
ex.printStackTrace()
}
} else { //addToHistory("Connected to: " + serverURI);
Log.e("TAG", "Connected to: $serverURI")
}
}
override fun connectionLost(cause: Throwable) {
Log.e("TAG", "The Connection was lost.")
}
override fun messageArrived(topic: String, message: MqttMessage) {
Log.e("TAG", "Incoming message: " + message.payload.toString())
}
override fun deliveryComplete(token: IMqttDeliveryToken) {}
})
val mqttConnectOptions = setUpConnectionOptions("MQ_CONNECTION_USERNAME", "MQ_CONNECTION_PASSWORD")
mqttConnectOptions.isAutomaticReconnect = true
mqttConnectOptions.isCleanSession = false
try {
mqttAndroidClient.connect(mqttConnectOptions, null, object : IMqttActionListener {
override fun onSuccess(asyncActionToken: IMqttToken) {
val disconnectedBufferOptions = DisconnectedBufferOptions()
disconnectedBufferOptions.isBufferEnabled = true
disconnectedBufferOptions.bufferSize = 100
disconnectedBufferOptions.isPersistBuffer = false
disconnectedBufferOptions.isDeleteOldestMessages = false
mqttAndroidClient.setBufferOpts(disconnectedBufferOptions)
try {
mqttAndroidClient.subscribe(publishTopicChannelName, 0, object : IMqttMessageListener {
override fun messageArrived(topic: String?, message: MqttMessage?) {
onConnectionSuccess(topic, message)
}
})
} catch (ex: MqttException) {
System.err.println("Exception whilst subscribing")
ex.printStackTrace()
}
}
override fun onFailure(asyncActionToken: IMqttToken, exception: Throwable) { //addToHistory("Failed to connect to: " + serverUri);
}
})
} catch (ex: MqttException) {
ex.printStackTrace()
}
}
private fun setUpConnectionOptions(username: String, password: String): MqttConnectOptions {
val connOpts = MqttConnectOptions()
connOpts.isCleanSession = true
connOpts.userName = username
connOpts.password = password.toCharArray()
return connOpts
}
From Java class I am calling it like below and getting message successfully:
private void subscribeMQForVideo() {
MqConnectionExtentionKt.connectMq(mContext, "mq_video_channel_name", (topic, mqttMessage) -> {
// message Arrived!
Log.e("TAG", "Message Video: " + topic + " : " + new String(mqttMessage.getPayload()));
return null;
});
}
To publish message similar extention I have created with little difference. (MqConnectionPublishExtention.kt)
fun Context.connectMq(onConnectionSuccess: (mqttAndroidClient: MqttAndroidClient?) -> Unit) {
val mClientId = BuildConfig.CLIENT_ID + System.currentTimeMillis()
val mqttAndroidClient = MqttAndroidClient(this, BuildConfig.MQ_SERVER_URI, mClientId)
mqttAndroidClient.setCallback(object : MqttCallbackExtended {
override fun connectComplete(reconnect: Boolean, serverURI: String) {
if (reconnect) { //addToHistory("Reconnected to : " + serverURI)
Log.e("TAG", "Reconnected to : $serverURI")
// Because Clean Session is true, we need to re-subscribe
onConnectionSuccess(mqttAndroidClient)
} else { //addToHistory("Connected to: " + serverURI);
Log.e("TAG", "Connected to: $serverURI")
}
}
override fun connectionLost(cause: Throwable) {
Log.e("TAG", "The Connection was lost.")
}
override fun messageArrived(topic: String, message: MqttMessage) {
Log.e("TAG", "Incoming message: " + message.payload.toString())
}
override fun deliveryComplete(token: IMqttDeliveryToken) {}
})
val mqttConnectOptions = setUpConnectionOptions(BuildConfig.MQ_CONNECTION_USERNAME, BuildConfig.MQ_CONNECTION_PASSWORD)
mqttConnectOptions.isAutomaticReconnect = true
mqttConnectOptions.isCleanSession = false
try {
mqttAndroidClient.connect(mqttConnectOptions, null, object : IMqttActionListener {
override fun onSuccess(asyncActionToken: IMqttToken) {
val disconnectedBufferOptions = DisconnectedBufferOptions()
disconnectedBufferOptions.isBufferEnabled = true
disconnectedBufferOptions.bufferSize = 100
disconnectedBufferOptions.isPersistBuffer = false
disconnectedBufferOptions.isDeleteOldestMessages = false
mqttAndroidClient.setBufferOpts(disconnectedBufferOptions)
onConnectionSuccess(mqttAndroidClient)
}
override fun onFailure(asyncActionToken: IMqttToken, exception: Throwable) { //addToHistory("Failed to connect to: " + serverUri);
}
})
} catch (ex: MqttException) {
ex.printStackTrace()
}
}
private fun setUpConnectionOptions(username: String, password: String): MqttConnectOptions {
val connOpts = MqttConnectOptions()
connOpts.isCleanSession = true
connOpts.userName = username
connOpts.password = password.toCharArray()
return connOpts
}
Publish message from java class
private void publishExerciseDataToMQChannel() {
MqConnectionPublishExtentionKt.connectMq(mContext, (mqttAndroidClient) -> {
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("params", mlParams);
jsonObject.put("workoutid", workoutId);
jsonObject.put("userid", model.getUserIdFromPrefs());
jsonObject.put("stream_id", streamDataModel.getStreamId());
MqttMessage message = new MqttMessage();
message.setPayload(jsonObject.toString().getBytes());
mqttAndroidClient.publish("Channel_name", message);
Log.e("TAG", message.getQos() + "");
if (!mqttAndroidClient.isConnected()) {
Log.e("TAG", mqttAndroidClient.getBufferedMessageCount() + " messages in buffer.");
}
} catch (MqttException e) {
System.err.println("Error Publishing: " + e.getMessage());
e.printStackTrace();
} catch (JSONException e) {
System.err.println("Error Publishing: " + e.getMessage());
e.printStackTrace();
}
return null;
});
}
Related
Here's what I have:
Here, incoming messages are sent to all users.
fun Route.chatSocket(roomController: RoomController) {
webSocket("/chat-socket" ) {
val session = call.sessions.get<ChatSession>()
if (session==null) {
close(CloseReason(CloseReason.Codes.VIOLATED_POLICY,"No session"))
return#webSocket
}
try {
roomController.onJoin(
username = session.username,
sessionId = session.sessionId,
socket = this
)
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
roomController.sendMessage(
senderUsername = session.username,
message = frame.readText()
)
}
}
} catch (e: MemberAlredyExistExpetion) {
call.respond(HttpStatusCode.Conflict)
} finally {
roomController.tryDisconnect(session.username)
}
}
}
And here is the roomController:
class RoomController(
private val messageDataSource: MessageDataSource
) {
private val members = ConcurrentHashMap<String,Member>()
fun onJoin(
username:String,
sessionId:String,
socket: WebSocketSession
) {
if (members.containsKey(username)) {
throw MemberAlredyExistExpetion()
}
members[username] = Member(
username = username,
sessionId = sessionId,
socket = socket
)
}
suspend fun sendMessage(senderUsername: String, message: String) {
members.values.forEach { member ->
val messageEntity = Message(
text = message,
username = senderUsername,
timestamp = System.currentTimeMillis()
)
messageDataSource.insertMessage(messageEntity)
val parsedMessage = Json.encodeToString(messageEntity)
member.socket.send(Frame.Text(parsedMessage))
}
}
Here I have tried to implement a command with which I can write directly to a user. But it don`t work.
fun Route.chatToOne(roomController: RoomController) {
webSocket("/whisper") {
val session = call.sessions.get<ChatSessionToOne>()
if (session==null) {
close(CloseReason(CloseReason.Codes.VIOLATED_POLICY,"No Session"))
return#webSocket
}
try {
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
roomController.sendMessageToOneUser(
senderUsername = session.username,
targetUsername = session.targetusername,
message = frame.readText()
)
}
}
} catch (e: MemberAlredyExistExpetion) {
call.respond(HttpStatusCode.Conflict)
}
}
}
And here the code from the roomController:
suspend fun sendMessageToOneUser(senderUsername: String, targetUsername :String, message: String) {
members.values.forEach { member ->
if (member.username == targetUsername) {
val messageEntity = Message(
text = message,
username = targetUsername,
timestamp = System.currentTimeMillis()
)
messageDataSource.insertMessage(messageEntity)
val parsedMessage = Json.encodeToString(messageEntity)
member.socket.send(Frame.Text(parsedMessage))
}
}
}
How can I write to only one user with an existing WebSocket connection instead of to all users?
I am using msal-flutter in one of the flutter app.
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.10.0"
Getting following error in Android app.
E/MethodChannel#msal_flutter(28234): Failed to handle method call
E/MethodChannel#msal_flutter(28234): kotlin.UninitializedPropertyAccessException: lateinit property mainActivity has not been initialized
E/MethodChannel#msal_flutter(28234): at com.signify.msal_flutter.MsalFlutterPlugin.initialize(MsalFlutterPlugin.kt:230)
E/MethodChannel#msal_flutter(28234): at com.signify.msal_flutter.MsalFlutterPlugin.onMethodCall(MsalFlutterPlugin.kt:125)
E/MethodChannel#msal_flutter(28234): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
E/MethodChannel#msal_flutter(28234): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
E/MethodChannel#msal_flutter(28234): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:692)
E/MethodChannel#msal_flutter(28234): at android.os.MessageQueue.nativePollOnce(Native Method)
E/MethodChannel#msal_flutter(28234): at android.os.MessageQueue.next(MessageQueue.java:335)
E/MethodChannel#msal_flutter(28234): at android.os.Looper.loop(Looper.java:183)
E/MethodChannel#msal_flutter(28234): at android.app.ActivityThread.main(ActivityThread.java:7660)
E/MethodChannel#msal_flutter(28234): at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#msal_flutter(28234): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
E/MethodChannel#msal_flutter(28234): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
I guess method registerWith is not calling.
Following is the code using in MsalFlutterPlugin class:
#Suppress("SpellCheckingInspection")
public class MsalFlutterPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "msal_flutter")
channel.setMethodCallHandler(this);
}
companion object {
lateinit var mainActivity : Activity
lateinit var msalApp: IMultipleAccountPublicClientApplication
fun isClientInitialized() = ::msalApp.isInitialized
#JvmStatic
fun registerWith(registrar: Registrar) {
Log.d("MsalFlutter","Registering plugin")
val channel = MethodChannel(registrar.messenger(), "msal_flutter")
channel.setMethodCallHandler(MsalFlutterPlugin())
mainActivity = registrar.activity()
}
fun getAuthCallback(result: Result) : AuthenticationCallback
{
Log.d("MsalFlutter", "Getting the auth callback object")
return object : AuthenticationCallback
{
override fun onSuccess(authenticationResult : IAuthenticationResult){
Log.d("MsalFlutter", "Authentication successful")
Handler(Looper.getMainLooper()).post {
result.success(authenticationResult.accessToken)
}
}
override fun onError(exception : MsalException)
{
Log.d("MsalFlutter","Error logging in!")
Log.d("MsalFlutter", exception.message)
Handler(Looper.getMainLooper()).post {
result.error("AUTH_ERROR", "Authentication failed", exception.localizedMessage)
}
}
override fun onCancel(){
Log.d("MsalFlutter", "Cancelled")
Handler(Looper.getMainLooper()).post {
result.error("CANCELLED", "User cancelled", null)
}
}
}
}
private fun getApplicationCreatedListener(result: Result) : IPublicClientApplication.ApplicationCreatedListener {
Log.d("MsalFlutter", "Getting the created listener")
return object : IPublicClientApplication.ApplicationCreatedListener
{
override fun onCreated(application: IPublicClientApplication) {
Log.d("MsalFlutter", "Created successfully")
msalApp = application as MultipleAccountPublicClientApplication
result.success(true)
}
override fun onError(exception: MsalException?) {
Log.d("MsalFlutter", "Initialize error")
result.error("INIT_ERROR", "Error initializting client", exception?.localizedMessage)
}
}
}
}
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
val scopesArg : ArrayList<String>? = call.argument("scopes")
val scopes: Array<String>? = scopesArg?.toTypedArray()
val clientId : String? = call.argument("clientID")
val authority : String? = call.argument("authorityURL")
Log.d("A_MsalFlutter","Got scopes: ${scopes.toString()}")
Log.d("A_MsalFlutter","Got cleintId: $clientId")
Log.d("A_MsalFlutter","Got authority: $authority")
when(call.method){
"logout" -> Thread(Runnable{logout(result)}).start()
"init" -> initialize(clientId, authority, result)
"acquireTokenInteractively" -> Thread(Runnable {acquireToken(scopes, result)}).start()
"acquireTokenSilent" -> Thread(Runnable {acquireTokenSilent(scopes, result)}).start()
else -> result.notImplemented()
}
}
private fun acquireToken(scopes : Array<String>?, result: Result)
{
Log.d("MsalFlutter", "acquire token called")
// check if client has been initialized
if(!isClientInitialized()){
Log.d("MsalFlutter","Client has not been initialized")
Handler(Looper.getMainLooper()).post {
result.error("NO_CLIENT", "Client must be initialized before attempting to acquire a token.", null)
}
}
//check scopes
if(scopes == null){
Log.d("MsalFlutter", "no scope")
result.error("NO_SCOPE", "Call must include a scope", null)
return
}
//remove old accounts
while(msalApp.accounts.any()){
Log.d("MsalFlutter","Removing old account")
msalApp.removeAccount(msalApp.accounts.first())
}
//acquire the token
msalApp.acquireToken(mainActivity, scopes, getAuthCallback(result))
}
private fun acquireTokenSilent(scopes : Array?, result: Result)
{
Log.d("MsalFlutter", "Called acquire token silent")
// check if client has been initialized
if(!isClientInitialized()){
Log.d("MsalFlutter","Client has not been initialized")
Handler(Looper.getMainLooper()).post {
result.error("NO_CLIENT", "Client must be initialized before attempting to acquire a token.", null)
}
}
//check the scopes
if(scopes == null){
Log.d("MsalFlutter", "no scope")
Handler(Looper.getMainLooper()).post {
result.error("NO_SCOPE", "Call must include a scope", null)
}
return
}
//ensure accounts exist
if(msalApp.accounts.isEmpty()){
Handler(Looper.getMainLooper()).post {
result.error("NO_ACCOUNT", "No account is available to acquire token silently for", null)
}
return
}
//acquire the token and return the result
val res = msalApp.acquireTokenSilent(scopes, msalApp.accounts[0], msalApp.configuration.defaultAuthority.authorityURL.toString())
Handler(Looper.getMainLooper()).post {
result.success(res.accessToken)
}
}
private fun initialize(clientId: String?, authority: String?, result: Result)
{
Log.d("MsalFlutter", "inside the initialize block")
//ensure clientid provided
if(clientId == null){
Log.d("MsalFlutter","error no clientId")
result.error("NO_CLIENTID", "Call must include a clientId", null)
return
}
//if already initialized, ensure clientid hasn't changed
if(isClientInitialized()){
Log.d("MsalFlutter","Client already initialized.")
if(msalApp.configuration.clientId == clientId)
{
result.success(true)
} else {
result.error("CHANGED_CLIENTID", "Attempting to initialize with multiple clientIds.", null)
}
}
// if authority is set, create client using it, otherwise use default
if(authority != null){
Log.d("MsalFlutter", "Authority not null")
Log.d("MsalFlutter", "Creating with: $clientId - $authority")
PublicClientApplication.create(mainActivity.applicationContext, clientId, authority, getApplicationCreatedListener(result))
}else{
Log.d("MsalFlutter", "Authority null")
PublicClientApplication.create(mainActivity.applicationContext, clientId, getApplicationCreatedListener(result))
}
}
private fun logout(result: Result){
while(msalApp.accounts.any()){
Log.d("MsalFlutter","Removing old account")
msalApp.removeAccount(msalApp.accounts.first())
}
Handler(Looper.getMainLooper()).post {
result.success(true)
}
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
msal_auth.dart::
class MsalAuth {
static const MethodChannel _channel =
const MethodChannel('msal_flutter');
//Configuration parameters
String _clientId, _authority, _redirectURI;
MsalAuth(String clientID, {String authorityURL, String redirectURL}){
throw Exception("Direct call is not allowed. Please use PublicClientApplication static method");
}
MsalAuth._create(String clientId, {String authority, String redirectURI}) {
_clientId = clientId;
_authority = authority;
_redirectURI = redirectURI;
}
static Future publicClientApplication(String clientID, {String authorityURL, String redirectURL}) async{
var res = MsalAuth._create(clientID, authority: authorityURL, redirectURI: redirectURL);
await res._initialize();
return res;
}
Future _initialize() async {
var res = <String, dynamic>{'clientID': this._clientId};
//if authority has been set, add it aswell
if (this._authority != null) {
res["authorityURL"] = this._authority;
}
if (this._redirectURI != null) {
res["redirectURI"] = this._redirectURI;
}
try {
await _channel.invokeMethod('init', res);
} on PlatformException catch (e) {
throw _convertException(e);
}
}
/// Acquire a token interactively for the given [scopes]
Future acquireToken(List scopes) async {
var res = <String, dynamic>{'scopes': scopes};
try {
final String token = await _channel.invokeMethod('acquireTokenInteractively', res);
return token;
} on PlatformException catch (e) {
throw _convertException(e);
}
}
/// Acquire a token silently, with no user interaction, for the given [scopes]
Future acquireTokenSilent(List scopes) async {
var res = <String, dynamic>{'scopes': scopes};
try {
final String token =
await _channel.invokeMethod('acquireTokenSilently', res);
return token;
} on PlatformException catch (e) {
throw _convertException(e);
}
}
Future logout() async {
try {
await _channel.invokeMethod('logout', <String, dynamic>{});
} on PlatformException catch (e) {
throw _convertException(e);
}
}
MsalException _convertException(PlatformException e) {
switch (e.code) {
case "CANCELLED":
return MsalUserCancelledException();
case "NO_SCOPE":
return MsalInvalidScopeException();
case "NO_ACCOUNT":
return MsalNoAccountException();
case "NO_CLIENTID":
return MsalInvalidConfigurationException("Client Id not set");
case "INVALID_AUTHORITY":
return MsalInvalidConfigurationException("Invalid authroity set.");
case "CONFIG_ERROR":
return MsalInvalidConfigurationException(
"Invalid configuration, please correct your settings and try again");
case "NO_CLIENT":
return MsalUninitializedException();
case "CHANGED_CLIENTID":
return MsalChangedClientIdException();
case "INIT_ERROR":
return MsalInitializationException();
case "AUTH_ERROR":
default:
return MsalException("Authentication error");
}
}
}
Server-side language: Python
Client-side language: Kotlin, Android
It receives data from different lines at one line.
class SampleActivity : AppCompatActivity() {
private val TAG = "SAMPLE_ACTIVITY_TAG"
private lateinit var editTextMessage: EditText
private val IPs = listOf("192.168.100.30")
private val PORTs = listOf(5050)
private val INDEX_SERVER = 0
private var socket: Socket? = null
private val IP = IPs[INDEX_SERVER]
private val PORT = PORTs[INDEX_SERVER]
private lateinit var outPrintWriter: PrintWriter
private val CONNECTION_TOKEN = "fab2bd65f67193d761c06b07a708d3232f0d8569"
private val TAG_CONNECT_START = "[CONNECT]"
private val TAG_CONNECT_END = "[/CONNECT]"
private val TAG_TRANSFER_START = "[TRANSFER]"
private val TAG_TRANSFER_END = "[/TRANSFER]"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
editTextMessage = findViewById(R.id.editTextMessage)
CoroutineScope(Dispatchers.IO).launch {
connect()
}
buttonSubmit.setOnClickListener {
if(editTextMessage.text.toString().trim().isEmpty()){
return#setOnClickListener
}
CoroutineScope(Dispatchers.IO).launch {
send(messageConverter(TAG_TRANSFER_START + editTextMessage.text.toString().trim() + TAG_TRANSFER_END))
}
}
}
override fun onDestroy() {
outPrintWriter.close()
super.onDestroy()
}
private fun messageConverter(text: String): String {
Log.w(TAG, "[CONVERTING] \nMessage: $text\nAscii: ${text.toASCII()}")
return text.toASCII() // Defined in extention.kt
}
private suspend fun connect() {
try {
Log.w(TAG, "[CONNECTING] Connecting to: $IP:$PORT")
socket = Socket(IP, PORT)
socket?.receiveBufferSize = 1024
socket?.soTimeout = 1 * 60 * 60
outPrintWriter = PrintWriter(socket!!.getOutputStream())
send(messageConverter(TAG_CONNECT_START + CONNECTION_TOKEN + TAG_CONNECT_END))
receive()
} catch (uHE: UnknownHostException) {
Log.e(TAG, "[ERROR] UnknownHostException, ${uHE.message}", uHE)
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] IOException, ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] Exception, ${e.message}", e)
}
}
private suspend fun send(text: String) {
try {
Log.w(TAG, "[SENDING] Message: $text")
outPrintWriter.print(text)
outPrintWriter.flush()
Log.w(TAG, "[SENDING] finished .")
clearMessageBox()
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] IOException, ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] IOException, ${e.message}", e)
}
}
private suspend fun receive() {
try {
val scanner = Scanner(
socket?.getInputStream()
)
val lines = ArrayList<String>()
var line: String?
while (scanner.hasNextLine()) {
Logger.w(TAG, "[RECEIVING] Scanner hasNext")
line = scanner.nextLine()
if (!line.isNullOrEmpty()) {
lines.add(line)
}
}
Logger.w(TAG, "[RECEIVING] Exit While loop : ${lines}")
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] Receive IOException error: ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] Receive Exception error: ${e.message}", e)
}
}
private suspend fun clearMessageBox() {
withContext(Main) {
editTextMessage.text.clear()
}
}
}
Logs:
[CONNECTING] Connecting to: 192.168.100.30:5050
[CONVERTING]
Message: [CONNECT]fab2bd65f67193d761c06b07a708d3232f0d8569[/CONNECT]
Ascii: 91,67,79,78,78,69,67,84,93,102,97,98,50,98,51,50,102,48,100,49,55,56,99,48,54,55,48,56,53,55,97,100,54,53,102,54,54,98,48,100,51,50,57,49,57,51,100,55,54,91,47,67,79,78,78,69,67,84,93
[SENDING] Message: 91,67,79,78,78,69,67,84,93,102,97,98,50,98,51,50,102,48,100,49,55,56,99,48,54,55,48,56,53,55,97,100,54,53,102,54,54,98,48,100,51,50,57,49,57,51,100,55,54,91,47,67,79,78,78,69,67,84,93
[SENDING] finished.
[RECEIVING] Scanner hasNext
[RECEIVING] Exit While loop : [OK1OK2]
Log: **[RECEIVING] Exit While loop :** shall be `[OK1, OK2]`
Server send messagesin every second twice
I just checked some questions and samples b, unfortunately in my case, no one solved the above issue
I am building an app that is supposed to send a file over a Bluetooth socket but for some reason i keep getting the same Exception. The error message says that the socket is already closed but i don't understand how or why it is getting closed before i attempt to read from it. The user is presented with an AlertDialog with options to choose from. they are supposed to: 1. Pair to a device. 2. send file to device (currently labeled 'connect' in dialog). The exception occurs after the devices are paired, when the connect option is selected. when the receiving device attempts to read from socket i get this exception:
Process: com.example.zemcd.toofxchange, PID: 1074
java.io.IOException: bt socket closed, read return: -1
at android.bluetooth.BluetoothSocket.read(BluetoothSocket.java:588)
at android.bluetooth.BluetoothInputStream.read(BluetoothInputStream.java:96)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:273)
at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
at java.io.FilterInputStream.read(FilterInputStream.java:107)
at kotlin.io.ByteStreamsKt.copyTo(IOStreams.kt:101)
at kotlin.io.ByteStreamsKt.copyTo$default(IOStreams.kt:98)
at kotlin.io.ByteStreamsKt.readBytes(IOStreams.kt:117)
at com.example.zemcd.toofxchange.BluetoothUtils$Companion.receiveFile(BluetoothUtils.kt:82)
at com.example.zemcd.toofxchange.ListenThread$run$acceptThread$1.run(BluetoothUtils.kt:104)
at java.lang.Thread.run(Thread.java:761)
below are the functions used for pair, unpair, and connect (to send data) along with the threads subclasses that used:
class BluetoothUtils {
companion object {
var listener: ListenThread? = null
val _UUID = UUID.fromString("a0e7e4c7-0e4e-43b7-9d18-659192512164")
val TAG = "BluetoothUtils"
val receiver = MainBTStatusReceiver()
fun initPairingServer(adapter: BluetoothAdapter){
var mmServerSocket: BluetoothServerSocket?
try {
var tmp = adapter.listenUsingRfcommWithServiceRecord(TAG, _UUID)
mmServerSocket = tmp
listener = ListenThread(mmServerSocket)
listener!!.start()
}catch (ioe: IOException){
Log.e(TAG, "Error initializing Bluetooth", ioe)
}
}
fun cancelListener() = listener!!.cancel()
fun connect(adapter: BluetoothAdapter, device: BluetoothDevice):Unit{
var btSocket: BluetoothSocket?
Log.d(TAG, "connect function called")
if (device.bondState==BluetoothDevice.BOND_NONE)return //to prompt user to pair
try {
adapter.cancelDiscovery()
btSocket = device.createRfcommSocketToServiceRecord(_UUID)
ConnectThread(btSocket).start()
}catch (ioe: IOException){
Log.e(TAG, "error connecting", ioe)
}
}
fun startPair(adapter: BluetoothAdapter, device: BluetoothDevice): Unit{
adapter.cancelDiscovery()
Log.d(TAG, device.bondState.toString())
device.createBond()
}
fun unPair(device: BluetoothDevice): Any = device::class.java.getMethod("removeBond").invoke(device)
fun sendFile(btSocket: BluetoothSocket){
val out = btSocket.outputStream.buffered()
out.use {
val msg = "hello".toByteArray()
Log.d(TAG, "sending data")
it.write(msg, 0, msg.size)
it.flush()
}
btSocket.close()
}
fun receiveFile(btSocket: BluetoothSocket){
Log.d(TAG, "receiveFile called")
val inStream = btSocket.inputStream.buffered()
//val bytes: ByteArray = ByteArray(1024)
val bytes: ByteArray = inStream.use { it.readBytes(1024)}
Log.d(TAG, bytes.toString())
btSocket.close()
}
}
}
class ListenThread(val btServSock: BluetoothServerSocket) : Thread(){
companion object {
val TAG = "ListenThread"
}
var btSocket: BluetoothSocket? = null
override fun run() {
super.run()
while (true){
try {
Log.d(TAG, "listening . . . ")
btSocket = btServSock.accept()
Log.d(TAG, btSocket.toString() + " was accepted")
val acceptThread = Thread(Runnable { BluetoothUtils.receiveFile(btSocket!!) })
acceptThread.start()
}catch (ioe: IOException){
Log.e(TAG, "Error", ioe)
break
}
}
}
fun cancel() = btServSock.close()
}
class ConnectThread(val btSocket: BluetoothSocket) : Thread(){
companion object {
val TAG = "Pairing Thread"
}
override fun run() {
super.run()
try {
Log.d(TAG, "attempting to connect")
btSocket.connect()
BluetoothUtils.sendFile(btSocket)
}catch (ioe: IOException){
Log.e(TAG, "error connecting", ioe)
btSocket.close()
}
}
}
and they are called like this inside of OnClickListener:
ops.forEach { it.setOnClickListener {
Toast.makeText(it.context, it.id.toString(), Toast.LENGTH_SHORT).show()
when(it.id){
R.id.statOp -> {}
R.id.connectOp -> {
Log.d(TAG, "connectOp reached")
BluetoothUtils.connect(BluetoothAdapter.getDefaultAdapter(), btDevice)
dialog!!.dismiss()
}
R.id.pairOp -> {
Log.d(TAG, "pairOp reached")
mReceiver.setFocus(this#DeviceHolder)
BluetoothUtils.startPair(BluetoothAdapter.getDefaultAdapter(), btDevice)
Log.d(TAG, "start pair complete")
dialog!!.dismiss()
}
R.id.unPairOp -> {
Log.d(TAG, "unPairOp reached")
mReceiver.setFocus(this#DeviceHolder)
BluetoothUtils.unPair(btDevice)
Log.d(TAG, "unpair complete")
dialog!!.dismiss()
}
R.id.sendOp -> {}
}
} }
and here is my with resources function:
inline fun <T: AutoCloseable, U> withResources(resource: T, fn: (T) -> U) : U{
try {
return fn(resource)
}finally {
resource.close()
}
}
please help me find my mistake. if other pieces of my code are needed let me know and i will post them. thank you for your help.
i am creating an app that scans for and pairs to Bluetooth devices. i am displaying the devices in a RecyclerView and indicating bond state by coloring the ViewHolder for that device. my problem is that the color of the ViewHolder is only changed after scanning for devices again and i want it to immediately update the color on pair or unpair. i am attempting to do this through use of a broadcast receiver but i am unable to get a reference to the correct ViewHolder. how can i achieve this? i am including my code below for my RecyclerView.Adapter and my BluetoothUtils file containing the broadcast receiver. thanks in advance. my adapter:
class DeviceAdapter(val mContext : Context) : RecyclerView.Adapter<DeviceAdapter.DeviceHolder>() {
companion object {
val TAG = "Device Adapter"
fun DeviceHolder.setColor(bonded: Boolean):Unit{
val background = if (bonded)Color.CYAN else Color.TRANSPARENT
this.itemView.setBackgroundColor(background)
}
}
val mDevices = ArrayList<BluetoothDevice>()
fun updateItems(list: ArrayList<BluetoothDevice>) {
mDevices.clear()
mDevices.addAll(list)
Log.d(TAG, "updating items : $mDevices")
notifyDataSetChanged()
}
fun ViewGroup.inflate(#LayoutRes res: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(mContext).inflate(res, this, attachToRoot)
}
override fun onBindViewHolder(holder: DeviceHolder, position: Int) {
Log.d(TAG, "onBindViewHolder called!")
holder.bindItems(mDevices.get(position))
if (mDevices.get(position).bondState==BluetoothDevice.BOND_BONDED) {
holder.itemView.setBackgroundColor(CYAN)
} else {
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): DeviceAdapter.DeviceHolder {
Log.d(TAG, "onCreateViewHolder called!")
val v = parent!!.inflate(R.layout.device_item, false)
return DeviceHolder(v)
}
override fun getItemCount(): Int {
return mDevices.size
}
inner class DeviceHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val nameView = itemView.findViewById(R.id.nameView) as TextView
val addrView = itemView.findViewById(R.id.addressView) as TextView
var dialog: AlertDialog? = null;
fun bindItems(btDevice: BluetoothDevice) {
Log.d(TAG, "holder created!")
nameView.text = btDevice.name ?: "Unknown"
addrView.text = btDevice.address
itemView.setOnClickListener {
dialog = AlertDialog.Builder(it.context)
.setTitle("Options")
.setView(R.layout.options_dialog_layout)
.setNegativeButton("Cancel", DialogInterface.OnClickListener { _, which -> })
.create()
dialog!!.show()
val ops = listOf(
dialog!!.findViewById(R.id.statOp),
dialog!!.findViewById(R.id.pairOp),
dialog!!.findViewById(R.id.connectOp),
dialog!!.findViewById(R.id.sendOp),
dialog!!.findViewById(R.id.unPairOp)
)
ops.forEach { it.setOnClickListener {
Toast.makeText(it.context, it.id.toString(), Toast.LENGTH_SHORT).show()
when(it.id){
R.id.statOp -> {}
R.id.connectOp -> {
Log.d(TAG, "connectOp reached")
BluetoothReflection.connectDevice(btDevice)
dialog!!.dismiss()
}// BluetoothUtils.connect(BluetoothAdapter.getDefaultAdapter(), btDevice)
R.id.pairOp -> {
Log.d(TAG, "pairOp reached")
BluetoothUtils.startPair(BluetoothAdapter.getDefaultAdapter(), btDevice)
if (btDevice.bondState==BluetoothDevice.BOND_BONDED){
this#DeviceHolder.itemView.setBackgroundColor(CYAN) //doesn't work
}
Log.d(TAG, "start pair complete")
dialog!!.dismiss()
}//
R.id.unPairOp -> {//no executable code found here
Log.d(TAG, "unPairOp reached")
BluetoothUtils.unPair(btDevice)
if (btDevice.bondState==BluetoothDevice.BOND_NONE){
this#DeviceHolder.itemView.setBackgroundColor(Color.TRANSPARENT) //doesn't work
}
Log.d(TAG, "unpair complete")
dialog!!.dismiss()
}
R.id.sendOp -> {}
}
} }
}
}
}
}
and my BluetoothUtils:
class BluetoothUtils {
companion object {
var listener: ListenThread? = null
val _UUID = UUID.fromString("a0e7e4c7-0e4e-43b7-9d18-659192512164")
val TAG = "BluetoothUtils"
val receiver = MainBTStatusReceiver()
fun initPairingServer(adapter: BluetoothAdapter){
var mmServerSocket: BluetoothServerSocket?
try {
var tmp = adapter.listenUsingRfcommWithServiceRecord(TAG, _UUID)
mmServerSocket = tmp
listener = ListenThread(mmServerSocket)
listener!!.start()
}catch (ioe: IOException){
Log.e(TAG, "Error initializing Bluetooth", ioe)
}
}
fun cancelListener() = listener!!.cancel()
fun connect(adapter: BluetoothAdapter, device: BluetoothDevice){
var btSocket: BluetoothSocket?
try {
adapter.cancelDiscovery()
btSocket = device.createRfcommSocketToServiceRecord(_UUID)
PairingThread(btSocket).start()
}catch (ioe: IOException){
Log.e(TAG, "error connecting", ioe)
}
}
fun startPair(adapter: BluetoothAdapter, device: BluetoothDevice): Unit{
adapter.cancelDiscovery()
Log.d(TAG, device.bondState.toString())
device.createBond()
}
fun unPair(device: BluetoothDevice): Any = device::class.java.getMethod("removeBond").invoke(device)
}
}
class ListenThread(val btServSock: BluetoothServerSocket) : Thread(){
companion object {
val TAG = "ListenThread"
}
var btSocket: BluetoothSocket? = null
override fun run() {
super.run()
while (true){
try {
Log.d(TAG, "listening . . . ")
btSocket = btServSock.accept()
}catch (ioe: IOException){
Log.e(TAG, "Error", ioe) // SHOULD HANDLE FAILURE OF LISTENER INSTANTIATION
break
}
//manage connection here
//with either BluetoothUtils function
//or BluetoothSocket extension
}
}
fun cancel() = btServSock.close()
}
class PairingThread(val btSocket: BluetoothSocket) : Thread(){
companion object {
val TAG = "Pairing Thread"
}
override fun run() {
super.run()
try {
Log.d(TAG, "attempting to connect")
btSocket.connect()
}catch (ioe: IOException){
Log.e(TAG, "error connecting", ioe)
btSocket.close()
}
}
}
class MainBTStatusReceiver(): BroadcastReceiver(){
val TAG = "MainBTStatusReceiver"
var mAdapter: DeviceAdapter? = null
fun setAdapter(adapter: DeviceAdapter){
mAdapter = adapter
}
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
val devExtra = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice
when(action){
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
when(device.bondState){
BluetoothDevice.BOND_BONDED -> {
Log.d(TAG, "BONDED")
}
BluetoothDevice.BOND_BONDING -> {Log.d(TAG, "BONDING")}
BluetoothDevice.BOND_NONE -> {Log.d(TAG, "NONE")}
}
}
}
}
Add boolean or int to BluetoothDevice model for managing view.
For example,
BluetoothDevice: added isOn state. (Sorry, it's Java)
class BluetoothDevice {
boolean isOn;
public boolean isOn() {
return isOn;
}
public void setOn(boolean isOn) {
this.isOn = isOn;
}
}
DeviceHolder: changed color of view
fun bindItems(btDevice: BluetoothDevice) {
stateView.textColor = btDevice.isOn() ? Color.RED : Color.GREEN
}
DeviceAdapter: added getItems
fun getItems() {
return mDevices
}
If you want to change isOn state, change model and notify it.
adapter.getItems().get(i).setOn(true);
adapter.notifyDataSetChanged();
I like the above answer as well but the way that i got this done was to pass the BroadcastReceiver into the DeviceAdapter :
class DeviceAdapter(val mContext:Context, val mReceiver:MainBTStatusReceiver) : RecyclerView.Adapter<DeviceAdapter.DeviceHolder>()
and then made a ViewHolder a member of the BroadcastReceiver and a setter function for the ViewHolder named setFocus. before calling any functions from the BluetoothUtils class i called the setFocus function and then the broadcast receiver modifies the color of the view that focus is currently set too. I do have some concern that this might not be reliable as the most accurate method to modify the correct ViewHolder every time. if you see any problem with this please comment to let me know.
my updated BroadcastReceiver:
class MainBTStatusReceiver(): BroadcastReceiver() {
val TAG = "MainBTStatusReceiver"
var holder: DeviceAdapter.DeviceHolder? = null
fun setFocus(holder: DeviceAdapter.DeviceHolder) {
this.holder = holder
}
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
when (action) {
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
when (device.bondState) {
BluetoothDevice.BOND_BONDED -> {
holder!!.itemView.setBackgroundColor(Color.CYAN)
Log.d(TAG, "BONDED")
}
BluetoothDevice.BOND_BONDING -> {
Log.d(TAG, "BONDING")
}
BluetoothDevice.BOND_NONE -> {
holder!!.itemView.setBackgroundColor(Color.TRANSPARENT)
Log.d(TAG, "NONE")
}
}
}
}
}
}
and the two statements in my when expression that call setFocus():
R.id.pairOp -> {
Log.d(TAG, "pairOp reached")
mReceiver.setFocus(this#DeviceHolder)
BluetoothUtils.startPair(BluetoothAdapter.getDefaultAdapter(), btDevice)
Log.d(TAG, "start pair complete")
dialog!!.dismiss()
}
R.id.unPairOp -> {
Log.d(TAG, "unPairOp reached")
mReceiver.setFocus(this#DeviceHolder)
BluetoothUtils.unPair(btDevice)
Log.d(TAG, "unpair complete")
dialog!!.dismiss()
}