completed android-example and arduino example co-working

This commit is contained in:
strawmanbobi
2026-01-27 20:22:00 +08:00
parent 12fa4d886b
commit 93685d90f0
32 changed files with 989 additions and 489 deletions

View File

@@ -1,51 +0,0 @@
package net.irext.ircontrol.controller;
import com.google.gson.Gson;
import net.irext.decode.sdk.bean.ACStatus;
import net.irext.ircontrol.controller.base.ControlCommand;
import org.jspecify.annotations.NonNull;
import java.util.Base64;
/**
* Filename: ControlCommand.java
* Revised: Date: 2026-01-22
* Revision: Revision: 1.0
* <p>
* Description: Remote command to Arduino remote
* <p>
* Revision log:
* 2026-01-22: created by strawmanbobi
*/
public class ArduinoControlCommand extends ControlCommand {
public ArduinoControlCommand(int keyCode, ACStatus acStatus) {
this.keyCode = keyCode;
this.acStatus = acStatus;
}
public ArduinoControlCommand() {
}
public int getKeyCode() {
return keyCode;
}
public void setKeyCode(int keyCode) {
this.keyCode = keyCode;
}
public ACStatus getAcStatus() {
return acStatus;
}
public void setAcStatus(ACStatus acStatus) {
this.acStatus = acStatus;
}
@Override
public @NonNull String toString() {
String jsonStr = new Gson().toJson(this);
return Base64.getEncoder().encodeToString(jsonStr.getBytes());
}
}

View File

@@ -2,35 +2,218 @@ package net.irext.ircontrol.controller;
import android.content.Context;
import android.util.Log;
import com.google.gson.Gson;
import net.irext.decode.sdk.bean.ACStatus;
import net.irext.ircontrol.controller.base.IRemote;
import net.irext.ircontrol.controller.base.ControlCommand;
import net.irext.ircontrol.controller.base.ControlHelper;
import net.irext.ircontrol.controller.base.Remote;
import org.jspecify.annotations.NonNull;
import static net.irext.ircontrol.controller.ArduinoSocket.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Base64;
/**
* Filename: ArduinoRemote.java
* Revised: Date: 2026-01-18
* Revision: Revision: 1.0
* <p>
* Description: Remote implementation by Arduino
* Description: Communication interface to Arduino
* <p>
* Revision log:
*2026-01-18: created by strawmanbobi
*/
public class ArduinoRemote implements IRemote {
public class ArduinoRemote extends Remote {
private static final String TAG = ArduinoRemote.class.getSimpleName();
Context mContext;
ArduinoSocket mArduinoSocket;
public static final int EMITTER_DISCONNECTED = 0;
public static final int EMITTER_CONNECTED = 1;
public static final int EMITTER_WORKING = 2;
public ArduinoRemote(Context context, ArduinoSocket socket) {
mContext = context;
mArduinoSocket = socket;
public static final int EMITTER_PORT = 8000;
public static final String A_REQUEST_HELLO = "a_hello";
public static final String E_RESPONSE_HELLO = "e_hello";
public static final String A_REQUEST_BIN = "a_bin";
public static final String E_RESPONSE_BIN = "e_bin";
public static final String A_REQUEST_CTRL = "a_control";
public static final String E_RESPONSE_CTRL = "e_control";
private Socket emitterConn = null;
private int connectionStatus = EMITTER_DISCONNECTED;
private IRSocketEmitterCallback callback;
private Context mContext = null;
private static ArduinoRemote mInstance;
public static ArduinoRemote getInstance(Context context, IRSocketEmitterCallback callback) {
if (mInstance == null) {
mInstance = new ArduinoRemote(context, callback);
}
return mInstance;
}
@Override
public int irControl(int category, int subCategory, int keyCode) {
public Context getContext() {
return mContext;
}
public void setContext(Context mContext) {
this.mContext = mContext;
}
public interface IRSocketEmitterCallback {
void onConnected();
void onDisconnected();
void onResponse(String response);
}
public ArduinoRemote(Context context, IRSocketEmitterCallback callback) {
this.mContext = context;
this.callback = callback;
}
public void setCallback(IRSocketEmitterCallback callback) {
this.callback = callback;
}
public int getConnectionStatus() {
return connectionStatus;
}
public void connectToEmitter(String ipAddress, String port) {
if (connectionStatus == EMITTER_DISCONNECTED) {
if (ipAddress == null || port == null) {
return;
}
new Thread(() -> {
try {
emitterConn = new Socket(ipAddress, Integer.parseInt(port));
emitterConn.setKeepAlive(true);
connectionStatus = EMITTER_CONNECTED;
onConnected();
BufferedReader in = new BufferedReader(new InputStreamReader(emitterConn.getInputStream()));
String response;
while ((response = in.readLine()) != null) {
onResponse(response);
}
if (callback != null) {
callback.onDisconnected();
}
connectionStatus = EMITTER_DISCONNECTED;
} catch (IOException ioException) {
Log.e(TAG, "Connection error: " + ioException.getMessage());
if (callback != null) {
callback.onDisconnected();
}
connectionStatus = EMITTER_DISCONNECTED;
}
}).start();
} else {
disconnect();
}
}
public void disconnect() {
try {
if (emitterConn != null && !emitterConn.isClosed()) {
emitterConn.close();
}
connectionStatus = EMITTER_DISCONNECTED;
} catch (IOException e) {
Log.e(TAG, "Error closing connection: " + e.getMessage());
}
}
public void sendHelloToEmitter() {
new Thread(() -> {
try {
Log.d(TAG, "sending a_hello to emitter");
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(A_REQUEST_HELLO);
} catch (IOException e) {
Log.e(TAG, "Error sending hello: " + e.getMessage());
}
}).start();
}
public void sendBinToEmitter(byte[] binContent, int categoryId, int subCate) {
if (binContent == null) {
Log.e(TAG, "binary bytes is null");
return;
}
String binBase64 = Base64.getEncoder().encodeToString(binContent);
String binStr = A_REQUEST_BIN + "," + categoryId + "," + subCate + "," + binBase64.length() + "," + binBase64;
Log.d(TAG, "sending bin in base64: " + binStr);
new Thread(() -> {
try {
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(binStr);
} catch (Exception e) {
Log.e(TAG, "Error sending binary data: " + e.getMessage());
}
}).start();
}
public void sendControlToEmitter(String command) {
String commandStr = A_REQUEST_CTRL + "," + command.length() + "," + command;
Log.d(TAG, "sending command in base64: " + commandStr);
new Thread(() -> {
try {
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(commandStr);
} catch (IOException e) {
Log.e(TAG, "Error sending control data: " + e.getMessage());
}
}).start();
}
public void sendDecodedToEmitter(String binContent) {
new Thread(() -> {
try {
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(binContent);
} catch (IOException e) {
Log.e(TAG, "Error sending decoded data: " + e.getMessage());
}
}).start();
}
private void onConnected() {
if (callback != null) {
Log.d(TAG, "the emitter is connected");
callback.onConnected();
}
}
private void onResponse(String response) {
if (response.startsWith(ArduinoRemote.E_RESPONSE_HELLO)) {
Log.d(TAG, "received e_hello");
} else if (response.startsWith(ArduinoRemote.E_RESPONSE_BIN)) {
Log.d(TAG, "received e_bin");
} else if (response.startsWith(ArduinoRemote.E_RESPONSE_CTRL)) {
connectionStatus = EMITTER_WORKING;
} else {
Log.e(TAG, "unexpected response : " + response);
}
callback.onResponse(response);
}
public void irControl(int category, int subCategory, int keyCode) {
Log.d(TAG, "irControl, category = " + category + ", subCategory = " + subCategory + ", keyCode = " + keyCode);
@@ -40,8 +223,40 @@ public class ArduinoRemote implements IRemote {
ArduinoControlCommand command = new ArduinoControlCommand(inputKeyCode, acStatus);
String controlCommand = command.toString();
mArduinoSocket.sendControlToEmitter(controlCommand);
sendControlToEmitter(controlCommand);
return 0;
}
}
private static class ArduinoControlCommand extends ControlCommand {
public ArduinoControlCommand(int keyCode, ACStatus acStatus) {
this.keyCode = keyCode;
this.acStatus = acStatus;
}
public ArduinoControlCommand() {
}
public int getKeyCode() {
return keyCode;
}
public void setKeyCode(int keyCode) {
this.keyCode = keyCode;
}
public ACStatus getAcStatus() {
return acStatus;
}
public void setAcStatus(ACStatus acStatus) {
this.acStatus = acStatus;
}
@Override
public @NonNull String toString() {
String jsonStr = new Gson().toJson(this);
return Base64.getEncoder().encodeToString(jsonStr.getBytes());
}
}
}

View File

@@ -1,188 +0,0 @@
package net.irext.ircontrol.controller;
import android.util.Log;
import android.util.Patterns;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Base64;
/**
* Filename: ArduinoSocket.java
* Revised: Date: 2026-01-18
* Revision: Revision: 1.0
* <p>
* Description: Communication interface to Arduino
* <p>
* Revision log:
*2026-01-18: created by strawmanbobi
*/
public class ArduinoSocket {
private static final String TAG = ArduinoSocket.class.getSimpleName();
public static final int EMITTER_DISCONNECTED = 0;
public static final int EMITTER_CONNECTED = 1;
public static final int EMITTER_WORKING = 2;
public static final int EMITTER_PORT = 8000;
public static final String A_REQUEST_HELLO = "a_hello";
public static final String E_RESPONSE_HELLO = "e_hello";
public static final String A_REQUEST_BIN = "a_bin";
public static final String E_RESPONSE_BIN = "e_bin";
public static final String A_REQUEST_CTRL = "a_control";
public static final String E_RESPONSE_CTRL = "e_control";
private Socket emitterConn = null;
private int connectionStatus = EMITTER_DISCONNECTED;
private IRSocketEmitterCallback callback;
public interface IRSocketEmitterCallback {
void onConnected();
void onDisconnected();
void onResponse(String response);
}
public ArduinoSocket(IRSocketEmitterCallback callback) {
this.callback = callback;
}
public void setCallback(IRSocketEmitterCallback callback) {
this.callback = callback;
}
public int getConnectionStatus() {
return connectionStatus;
}
public void connectToEmitter(String ipAddress, String port) {
if (connectionStatus == EMITTER_DISCONNECTED) {
if (ipAddress == null || port == null) {
return;
}
new Thread(() -> {
try {
emitterConn = new Socket(ipAddress, Integer.parseInt(port));
emitterConn.setKeepAlive(true);
connectionStatus = EMITTER_CONNECTED;
onConnected();
BufferedReader in = new BufferedReader(new InputStreamReader(emitterConn.getInputStream()));
String response;
while ((response = in.readLine()) != null) {
onResponse(response);
}
if (callback != null) {
callback.onDisconnected();
}
connectionStatus = EMITTER_DISCONNECTED;
} catch (IOException ioException) {
Log.e(TAG, "Connection error: " + ioException.getMessage());
if (callback != null) {
callback.onDisconnected();
}
connectionStatus = EMITTER_DISCONNECTED;
}
}).start();
} else {
disconnect();
}
}
public void disconnect() {
try {
if (emitterConn != null && !emitterConn.isClosed()) {
emitterConn.close();
}
connectionStatus = EMITTER_DISCONNECTED;
} catch (IOException e) {
Log.e(TAG, "Error closing connection: " + e.getMessage());
}
}
public void sendHelloToEmitter() {
new Thread(() -> {
try {
Log.d(TAG, "sending a_hello to emitter");
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(A_REQUEST_HELLO);
} catch (IOException e) {
Log.e(TAG, "Error sending hello: " + e.getMessage());
}
}).start();
}
public void sendBinToEmitter(byte[] binContent, int categoryId, int subCate) {
if (binContent == null) {
Log.e(TAG, "binary bytes is null");
return;
}
String binBase64 = Base64.getEncoder().encodeToString(binContent);
String binStr = A_REQUEST_BIN + "," + categoryId + "," + subCate + "," + binBase64.length() + "," + binBase64;
Log.d(TAG, "sending bin in base64: " + binStr);
new Thread(() -> {
try {
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(binStr);
} catch (Exception e) {
Log.e(TAG, "Error sending binary data: " + e.getMessage());
}
}).start();
}
public void sendControlToEmitter(String command) {
String commandStr = A_REQUEST_CTRL + "," + command.length() + "," + command;
Log.d(TAG, "sending command in base64: " + commandStr);
new Thread(() -> {
try {
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(commandStr);
} catch (IOException e) {
Log.e(TAG, "Error sending control data: " + e.getMessage());
}
});
}
public void sendDecodedToEmitter(String binContent) {
new Thread(() -> {
try {
PrintWriter out = new PrintWriter(emitterConn.getOutputStream(), true);
out.println(binContent);
} catch (IOException e) {
Log.e(TAG, "Error sending decoded data: " + e.getMessage());
}
}).start();
}
private void onConnected() {
if (callback != null) {
Log.d(TAG, "the emitter is connected");
callback.onConnected();
}
}
private void onResponse(String response) {
if (response.startsWith(ArduinoSocket.E_RESPONSE_HELLO)) {
;
} else if (response.startsWith(ArduinoSocket.E_RESPONSE_BIN)) {
;
} else if (response.startsWith(ArduinoSocket.E_RESPONSE_CTRL)) {
connectionStatus = EMITTER_WORKING;
} else {
Log.e(TAG, "unexpected response : " + response);
}
callback.onResponse(response);
}
}

View File

@@ -4,7 +4,8 @@ import android.content.Context;
import android.util.Log;
import net.irext.decode.sdk.IRDecode;
import net.irext.decode.sdk.bean.ACStatus;
import net.irext.ircontrol.controller.base.IRemote;
import net.irext.ircontrol.controller.base.ControlHelper;
import net.irext.ircontrol.controller.base.Remote;
/**
* Filename: PhoneRemote.java
@@ -16,19 +17,27 @@ import net.irext.ircontrol.controller.base.IRemote;
* Revision log:
*2026-01-18: created by strawmanbobi
*/
public class PhoneRemote implements IRemote {
public class PhoneRemote extends Remote {
private static final String TAG = PhoneRemote.class.getSimpleName();
IRDecode mIRDecode;
private Context mContext;
private IRDecode mIRDecode;
private static PhoneRemote mInstance;
Context mContext;
public PhoneRemote(Context context) {
mContext = context;
mIRDecode = new IRDecode();
}
@Override
public static PhoneRemote getInstance(Context context) {
if (mInstance == null) {
mInstance = new PhoneRemote(context);
}
return mInstance;
}
public int irControl(int category, int subCategory, int keyCode) {
int []decoded;
StringBuilder debugStr = new StringBuilder();

View File

@@ -1,4 +1,4 @@
package net.irext.ircontrol.controller;
package net.irext.ircontrol.controller.base;
import android.content.Context;
import android.hardware.ConsumerIrManager;
@@ -9,7 +9,7 @@ import net.irext.ircontrol.utils.ToastUtils;
import java.util.Objects;
import static net.irext.ircontrol.controller.base.IRemote.*;
import static net.irext.ircontrol.controller.base.Remote.*;
/**
* Filename: ControlHelper.java

View File

@@ -1,16 +1,16 @@
package net.irext.ircontrol.controller.base;
/**
* Filename: IRemote.java
* Filename: Remote.java
* Revised: Date: 2026-01-18
* Revision: Revision: 1.0
* <p>
* Description: IRemote interface
* Description: Remote interface
* <p>
* Revision log:
*2026-01-18: created by strawmanbobi
*/
public interface IRemote {
public abstract class Remote {
public static final int KEY_POWER = 0;
public static final int KEY_UP = 1;
@@ -24,6 +24,8 @@ public interface IRemote {
public static final int KEY_HOME = 9;
public static final int KEY_MENU = 10;
int irControl(int category, int subCategory, int keyCode);
int irControl(int category, int subCategory, int keyCode) {
return 0;
}
}

View File

@@ -3,6 +3,7 @@ package net.irext.ircontrol.ui.fragment;
import android.content.Context;
import android.graphics.Color;
import android.os.*;
import android.provider.ContactsContract;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -15,9 +16,8 @@ import net.irext.decode.sdk.IRDecode;
import net.irext.ircontrol.R;
import net.irext.ircontrol.bean.RemoteControl;
import net.irext.ircontrol.controller.ArduinoRemote;
import net.irext.ircontrol.controller.ArduinoSocket;
import net.irext.ircontrol.controller.PhoneRemote;
import net.irext.ircontrol.controller.base.IRemote;
import net.irext.ircontrol.controller.base.Remote;
import net.irext.ircontrol.ui.activity.ControlActivity;
import net.irext.ircontrol.utils.FileUtils;
import net.irext.ircontrol.utils.MessageUtils;
@@ -45,7 +45,8 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
private static final int CMD_GET_REMOTE_CONTROL = 0;
private ArduinoSocket mArduinoSocket;
private PhoneRemote mPhoneRemote;
private ArduinoRemote mArduinoRemote;
private MsgHandler mHandler;
@@ -96,12 +97,8 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
btnPlus.setOnClickListener(this);
btnMinus.setOnClickListener(this);
mEtEmitterIp = view.findViewById(R.id.emitter_ip);
mBtnConnect = view.findViewById(R.id.btn_connect_emitter);
mVWConnectStatus = view.findViewById(R.id.vw_connect_status);
// Initialize ArduinoSocket with callback
mArduinoSocket = new ArduinoSocket(new ArduinoSocket.IRSocketEmitterCallback() {
mPhoneRemote = PhoneRemote.getInstance(mParent);
mArduinoRemote = ArduinoRemote.getInstance(mParent, new ArduinoRemote.IRSocketEmitterCallback() {
@Override
public void onConnected() {
onEmitterConnected();
@@ -118,6 +115,10 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
}
});
mEtEmitterIp = view.findViewById(R.id.emitter_ip);
mBtnConnect = view.findViewById(R.id.btn_connect_emitter);
mVWConnectStatus = view.findViewById(R.id.vw_connect_status);
mBtnConnect.setOnClickListener(new View.OnClickListener() {
@Override
@@ -129,7 +130,7 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
ToastUtils.showToast(mParent, mParent.getString(R.string.input_emitter_ip_address), null);
return;
}
mArduinoSocket.connectToEmitter(emitterIp, String.valueOf(ArduinoSocket.EMITTER_PORT));
mArduinoRemote.connectToEmitter(emitterIp, String.valueOf(ArduinoRemote.EMITTER_PORT));
}
});
@@ -152,13 +153,13 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
@Override
public void onStop() {
super.onStop();
mArduinoSocket.disconnect();
mArduinoRemote.disconnect();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mArduinoSocket.disconnect();
mArduinoRemote.disconnect();
}
private void getRemote() {
@@ -202,7 +203,7 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
}
private void processEHello(String response) {
mArduinoSocket.sendHelloToEmitter();
mArduinoRemote.sendHelloToEmitter();
}
private void processEBin(String response) {
@@ -210,7 +211,7 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
mCurrentRemoteControl.getRemoteMap() + FileUtils.FILE_NAME_EXT;
byte []binContent = FileUtils.getByteArrayFromFile(binFileName);
if (null != binContent) {
mArduinoSocket.sendBinToEmitter(binContent, mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory());
mArduinoRemote.sendBinToEmitter(binContent, mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory());
} else {
Log.e(TAG, "emitter sender could not open the binary file");
ToastUtils.showToast(mParent, mParent.getString(R.string.file_could_not_open), Toast.LENGTH_SHORT);
@@ -222,11 +223,11 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
}
private void onEmitterResponse(String response) {
if (response.startsWith(ArduinoSocket.E_RESPONSE_HELLO)) {
if (response.startsWith(ArduinoRemote.E_RESPONSE_HELLO)) {
processEHello(response);
} else if (response.startsWith(ArduinoSocket.E_RESPONSE_BIN)) {
} else if (response.startsWith(ArduinoRemote.E_RESPONSE_BIN)) {
processEBin(response);
} else if (response.startsWith(ArduinoSocket.E_RESPONSE_CTRL)) {
} else if (response.startsWith(ArduinoRemote.E_RESPONSE_CTRL)) {
processECtrl(response);
} else {
Log.e(TAG, "unexpected response : " + response);
@@ -237,39 +238,38 @@ public class ControlFragment extends Fragment implements View.OnClickListener {
@Override
public void onClick(View v) {
vibrate(mParent);
IRemote remote = null;
Remote remote = null;
int keyCode = 0;
int id = v.getId();
if (id == R.id.iv_power) {
keyCode = IRemote.KEY_POWER;
keyCode = Remote.KEY_POWER;
} else if (id == R.id.iv_up) {
keyCode = IRemote.KEY_UP;
keyCode = Remote.KEY_UP;
} else if (id == R.id.iv_down) {
keyCode = IRemote.KEY_DOWN;
keyCode = Remote.KEY_DOWN;
} else if (id == R.id.iv_left) {
keyCode = IRemote.KEY_LEFT;
keyCode = Remote.KEY_LEFT;
} else if (id == R.id.iv_right) {
keyCode = IRemote.KEY_RIGHT;
keyCode = Remote.KEY_RIGHT;
} else if (id == R.id.iv_ok) {
keyCode = IRemote.KEY_OK;
keyCode = Remote.KEY_OK;
} else if (id == R.id.iv_plus) {
keyCode = IRemote.KEY_PLUS;
keyCode = Remote.KEY_PLUS;
} else if (id == R.id.iv_minus) {
keyCode = IRemote.KEY_MINUS;
keyCode = Remote.KEY_MINUS;
} else if (id == R.id.iv_back) {
keyCode = IRemote.KEY_BACK;
keyCode = Remote.KEY_BACK;
} else if (id == R.id.iv_home) {
keyCode = IRemote.KEY_HOME;
keyCode = Remote.KEY_HOME;
} else if (id == R.id.iv_menu) {
keyCode = IRemote.KEY_MENU;
keyCode = Remote.KEY_MENU;
}
if (mArduinoSocket.getConnectionStatus() == ArduinoSocket.EMITTER_WORKING) {
remote = new ArduinoRemote(mParent, mArduinoSocket);
if (mArduinoRemote.getConnectionStatus() == ArduinoRemote.EMITTER_WORKING) {
mArduinoRemote.irControl(mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory(), keyCode);
} else {
remote = new PhoneRemote(mParent);
mPhoneRemote.irControl(mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory(), keyCode);
}
remote.irControl(mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory(), keyCode);
}
private static class MsgHandler extends Handler {