From 2bcd983909916df9ced312a7762209c460a2c6d8 Mon Sep 17 00:00:00 2001 From: strawmanbobi Date: Wed, 14 Jan 2026 21:06:36 +0800 Subject: [PATCH] refactored android example --- .../ircontrol/controller/ArduinoRemote.java | 20 +++ .../ArduinoSocket.java} | 18 +- .../ircontrol/controller/ControlUtils.java | 87 ++++++++++ .../ircontrol/controller/PhoneRemote.java | 26 +++ .../controller/implementable/IRemote.java | 22 +++ .../ui/fragment/ControlFragment.java | 163 ++++-------------- arduino-example/src/configure.h | 8 +- 7 files changed, 204 insertions(+), 140 deletions(-) create mode 100644 android-example/app/src/main/java/net/irext/ircontrol/controller/ArduinoRemote.java rename android-example/app/src/main/java/net/irext/ircontrol/{utils/IRSocketEmitter.java => controller/ArduinoSocket.java} (92%) create mode 100644 android-example/app/src/main/java/net/irext/ircontrol/controller/ControlUtils.java create mode 100644 android-example/app/src/main/java/net/irext/ircontrol/controller/PhoneRemote.java create mode 100644 android-example/app/src/main/java/net/irext/ircontrol/controller/implementable/IRemote.java diff --git a/android-example/app/src/main/java/net/irext/ircontrol/controller/ArduinoRemote.java b/android-example/app/src/main/java/net/irext/ircontrol/controller/ArduinoRemote.java new file mode 100644 index 0000000..7fbeb6f --- /dev/null +++ b/android-example/app/src/main/java/net/irext/ircontrol/controller/ArduinoRemote.java @@ -0,0 +1,20 @@ +package net.irext.ircontrol.controller; + +import android.content.Context; +import net.irext.ircontrol.controller.implementable.IRemote; + +public class ArduinoRemote implements IRemote { + + Context mContext; + ArduinoSocket mArduinoSocket; + + public ArduinoRemote(Context context, ArduinoSocket socket) { + mContext = context; + mArduinoSocket = socket; + } + + @Override + public int irControl(int category, int subCategory, int keyCode) { + return 0; + } +} diff --git a/android-example/app/src/main/java/net/irext/ircontrol/utils/IRSocketEmitter.java b/android-example/app/src/main/java/net/irext/ircontrol/controller/ArduinoSocket.java similarity index 92% rename from android-example/app/src/main/java/net/irext/ircontrol/utils/IRSocketEmitter.java rename to android-example/app/src/main/java/net/irext/ircontrol/controller/ArduinoSocket.java index 9833c25..4fb6b1b 100644 --- a/android-example/app/src/main/java/net/irext/ircontrol/utils/IRSocketEmitter.java +++ b/android-example/app/src/main/java/net/irext/ircontrol/controller/ArduinoSocket.java @@ -1,6 +1,7 @@ -package net.irext.ircontrol.utils; +package net.irext.ircontrol.controller; import android.util.Log; +import android.util.Patterns; import java.io.BufferedReader; import java.io.IOException; @@ -10,11 +11,11 @@ import java.net.Socket; import java.util.Base64; /** - * Filename: IRSocketEmitter.java + * Filename: ArduinoSocket.java * Description: Handle socket communication with external IR emitter */ -public class IRSocketEmitter { - private static final String TAG = IRSocketEmitter.class.getSimpleName(); +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; @@ -42,7 +43,7 @@ public class IRSocketEmitter { void onResponse(String response); } - public IRSocketEmitter(IRSocketEmitterCallback callback) { + public ArduinoSocket(IRSocketEmitterCallback callback) { this.callback = callback; } @@ -164,4 +165,11 @@ public class IRSocketEmitter { public void processECtrl(String response) { // Handle control response if needed } + + public static boolean isValidIPv4(String ip) { + if (ip == null) { + return false; + } + return Patterns.IP_ADDRESS.matcher(ip).matches(); + } } \ No newline at end of file diff --git a/android-example/app/src/main/java/net/irext/ircontrol/controller/ControlUtils.java b/android-example/app/src/main/java/net/irext/ircontrol/controller/ControlUtils.java new file mode 100644 index 0000000..5283f0d --- /dev/null +++ b/android-example/app/src/main/java/net/irext/ircontrol/controller/ControlUtils.java @@ -0,0 +1,87 @@ +package net.irext.ircontrol.controller; + +import android.content.Context; +import android.hardware.ConsumerIrManager; +import net.irext.decode.sdk.bean.ACStatus; +import net.irext.decode.sdk.utils.Constants; +import net.irext.ircontrol.R; +import net.irext.ircontrol.utils.ToastUtils; + +import java.util.Objects; + +import static net.irext.ircontrol.controller.implementable.IRemote.*; + +public class ControlUtils { + + public static int translateKeyCode(int category, int keyCode, ACStatus acStatus) { + int inputKeyCode = 0; + + if (Constants.CategoryID.AIR_CONDITIONER.getValue() == category) { + acStatus.setAcPower(Constants.ACPower.POWER_OFF.getValue()); + acStatus.setAcMode(Constants.ACMode.MODE_COOL.getValue()); + acStatus.setAcTemp(Constants.ACTemperature.TEMP_24.getValue()); + acStatus.setAcWindSpeed(Constants.ACWindSpeed.SPEED_AUTO.getValue()); + acStatus.setAcWindDir(Constants.ACSwing.SWING_ON.getValue()); + acStatus.setChangeWindDir(0); + acStatus.setAcDisplay(0); + acStatus.setAcTimer(0); + acStatus.setAcSleep(0); + + switch(keyCode) { + case KEY_POWER: + // power key --> change power + inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_POWER.getValue(); + break; + case KEY_UP: + // up key --> change wind speed + inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_WIND_SPEED.getValue(); + break; + case KEY_DOWN: + // down key --> change wind dir + inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_WIND_DIR.getValue(); + break; + case KEY_RIGHT: + // right key --> change mode + inputKeyCode = Constants.ACFunction.FUNCTION_CHANGE_MODE.getValue(); + break; + case KEY_OK: + // center key --> fix wind dir + inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_SWING.getValue(); + break; + case KEY_PLUS: + // plus key --> temp up + inputKeyCode = Constants.ACFunction.FUNCTION_TEMPERATURE_UP.getValue(); + break; + case KEY_MINUS: + // minus key --> temp down + inputKeyCode = Constants.ACFunction.FUNCTION_TEMPERATURE_DOWN.getValue(); + break; + + default: + return -1; + } + } else { + inputKeyCode = keyCode; + } + return inputKeyCode; + } + + public static void transmitIr(Context context, int []decoded) { + // debug decoded value + StringBuilder decodedValue = new StringBuilder(); + for (int i = 0; i < Objects.requireNonNull(decoded).length; i++) { + decodedValue.append(decoded[i]); + decodedValue.append(","); + } + // send decoded integer array to IR emitter + ConsumerIrManager irEmitter = + (ConsumerIrManager) context.getSystemService(Context.CONSUMER_IR_SERVICE); + if (null != irEmitter && irEmitter.hasIrEmitter()) { + if (decoded.length > 0) { + irEmitter.transmit(38000, decoded); + } + } else { + ToastUtils.showToast(context, context.getString(R.string.ir_not_supported), null); + } + } +} diff --git a/android-example/app/src/main/java/net/irext/ircontrol/controller/PhoneRemote.java b/android-example/app/src/main/java/net/irext/ircontrol/controller/PhoneRemote.java new file mode 100644 index 0000000..91808f0 --- /dev/null +++ b/android-example/app/src/main/java/net/irext/ircontrol/controller/PhoneRemote.java @@ -0,0 +1,26 @@ +package net.irext.ircontrol.controller; + +import android.content.Context; +import net.irext.decode.sdk.IRDecode; +import net.irext.decode.sdk.bean.ACStatus; +import net.irext.ircontrol.controller.implementable.IRemote; + +public class PhoneRemote implements IRemote { + + IRDecode mIRDecode; + + Context mContext; + public PhoneRemote(Context context) { + mContext = context; + } + + @Override + public int irControl(int category, int subCategory, int keyCode) { + int []decoded; + ACStatus acStatus = new ACStatus(); + int inputKeyCode = ControlUtils.translateKeyCode(category, keyCode, acStatus); + decoded = mIRDecode.decodeBinary(inputKeyCode, acStatus); + ControlUtils.transmitIr(mContext, decoded); + return 0; + } +} diff --git a/android-example/app/src/main/java/net/irext/ircontrol/controller/implementable/IRemote.java b/android-example/app/src/main/java/net/irext/ircontrol/controller/implementable/IRemote.java new file mode 100644 index 0000000..82bcb66 --- /dev/null +++ b/android-example/app/src/main/java/net/irext/ircontrol/controller/implementable/IRemote.java @@ -0,0 +1,22 @@ +package net.irext.ircontrol.controller.implementable; + +import android.content.Context; +import net.irext.decode.sdk.IRDecode; + +public interface IRemote { + + public static final int KEY_POWER = 0; + public static final int KEY_UP = 1; + public static final int KEY_DOWN = 2; + public static final int KEY_LEFT = 3; + public static final int KEY_RIGHT = 4; + public static final int KEY_OK = 5; + public static final int KEY_PLUS = 6; + public static final int KEY_MINUS = 7; + public static final int KEY_BACK = 8; + public static final int KEY_HOME = 9; + public static final int KEY_MENU = 10; + + int irControl(int category, int subCategory, int keyCode); + +} diff --git a/android-example/app/src/main/java/net/irext/ircontrol/ui/fragment/ControlFragment.java b/android-example/app/src/main/java/net/irext/ircontrol/ui/fragment/ControlFragment.java index 3de7974..22352e9 100644 --- a/android-example/app/src/main/java/net/irext/ircontrol/ui/fragment/ControlFragment.java +++ b/android-example/app/src/main/java/net/irext/ircontrol/ui/fragment/ControlFragment.java @@ -2,7 +2,6 @@ package net.irext.ircontrol.ui.fragment; import android.content.Context; import android.graphics.Color; -import android.hardware.ConsumerIrManager; import android.os.*; import android.util.Log; import android.view.LayoutInflater; @@ -13,21 +12,18 @@ import android.widget.*; import androidx.appcompat.content.res.AppCompatResources; import androidx.fragment.app.Fragment; import net.irext.decode.sdk.IRDecode; -import net.irext.decode.sdk.bean.ACStatus; -import net.irext.decode.sdk.utils.Constants; 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.implementable.IRemote; import net.irext.ircontrol.ui.activity.ControlActivity; import net.irext.ircontrol.utils.FileUtils; import net.irext.ircontrol.utils.MessageUtil; import net.irext.ircontrol.utils.ToastUtils; import java.lang.ref.WeakReference; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Objects; - -import net.irext.ircontrol.utils.IRSocketEmitter; /** * Filename: ControlFragment.java @@ -48,19 +44,7 @@ public class ControlFragment extends Fragment implements View.OnClickListener { private static final int CMD_GET_REMOTE_CONTROL = 0; - private static final int KEY_POWER = 0; - private static final int KEY_UP = 1; - private static final int KEY_DOWN = 2; - private static final int KEY_LEFT = 3; - private static final int KEY_RIGHT = 4; - private static final int KEY_OK = 5; - private static final int KEY_PLUS = 6; - private static final int KEY_MINUS = 7; - private static final int KEY_BACK = 8; - private static final int KEY_HOME = 9; - private static final int KEY_MENU = 10; - - private IRSocketEmitter mIRSocketEmitter; + private ArduinoSocket mArduinoSocket; private MsgHandler mHandler; @@ -115,8 +99,8 @@ public class ControlFragment extends Fragment implements View.OnClickListener { mBtnConnect = view.findViewById(R.id.btn_connect_emitter); mVWConnectStatus = view.findViewById(R.id.vw_connect_status); - // Initialize IRSocketEmitter with callback - mIRSocketEmitter = new IRSocketEmitter(new IRSocketEmitter.IRSocketEmitterCallback() { + // Initialize ArduinoSocket with callback + mArduinoSocket = new ArduinoSocket(new ArduinoSocket.IRSocketEmitterCallback() { @Override public void onConnected() { onEmitterConnected(); @@ -139,11 +123,12 @@ public class ControlFragment extends Fragment implements View.OnClickListener { public void onClick(View v) { vibrate(mParent); String emitterIp = mEtEmitterIp.getText().toString(); - if (isIpAddress(emitterIp)) { + if (!ArduinoSocket.isValidIPv4(emitterIp)) { + Log.e(TAG, "IP address is invalid: " + emitterIp); ToastUtils.showToast(mParent, mParent.getString(R.string.input_emitter_ip_address), null); return; } - mIRSocketEmitter.connectToEmitter(emitterIp, String.valueOf(IRSocketEmitter.EMITTER_PORT)); + mArduinoSocket.connectToEmitter(emitterIp, String.valueOf(ArduinoSocket.EMITTER_PORT)); } }); @@ -189,66 +174,6 @@ public class ControlFragment extends Fragment implements View.OnClickListener { mIRDecode.closeBinary(); } - private int[] irControl(int keyCode) { - int inputKeyCode; - ACStatus acStatus = new ACStatus(); - /* decode SDK - decode according to key code */ - if (Constants.CategoryID.AIR_CONDITIONER.getValue() == - mCurrentRemoteControl.getCategoryId()) { - acStatus.setAcPower(Constants.ACPower.POWER_OFF.getValue()); - acStatus.setAcMode(Constants.ACMode.MODE_COOL.getValue()); - acStatus.setAcTemp(Constants.ACTemperature.TEMP_24.getValue()); - acStatus.setAcWindSpeed(Constants.ACWindSpeed.SPEED_AUTO.getValue()); - acStatus.setAcWindDir(Constants.ACSwing.SWING_ON.getValue()); - acStatus.setChangeWindDir(0); - acStatus.setAcDisplay(0); - acStatus.setAcTimer(0); - acStatus.setAcSleep(0); - - switch(keyCode) { - case KEY_POWER: - // power key --> change power - inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_POWER.getValue(); - break; - case KEY_UP: - // up key --> change wind speed - inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_WIND_SPEED.getValue(); - break; - case KEY_DOWN: - // down key --> change wind dir - inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_WIND_DIR.getValue(); - break; - case KEY_RIGHT: - // right key --> change mode - inputKeyCode = Constants.ACFunction.FUNCTION_CHANGE_MODE.getValue(); - break; - case KEY_OK: - // center key --> fix wind dir - inputKeyCode = Constants.ACFunction.FUNCTION_SWITCH_SWING.getValue(); - break; - case KEY_PLUS: - // plus key --> temp up - inputKeyCode = Constants.ACFunction.FUNCTION_TEMPERATURE_UP.getValue(); - break; - case KEY_MINUS: - // minus key --> temp down - inputKeyCode = Constants.ACFunction.FUNCTION_TEMPERATURE_DOWN.getValue(); - break; - - default: - return null; - } - } else { - inputKeyCode = keyCode; - } - - /* decode SDK - decode from binary */ - /* translate key code for AC according to the mapping above */ - /* ac status is useless for decoding devices other than AC, it's an optional parameter */ - /* change wind dir is an optional parameter, set to 0 as default */ - return mIRDecode.decodeBinary(inputKeyCode, acStatus); - } - private void onEmitterConnected() { Log.d(TAG, "the emitter is connected"); mParent.runOnUiThread(() -> { @@ -265,14 +190,14 @@ public class ControlFragment extends Fragment implements View.OnClickListener { } private void processEHello(String response) { - mIRSocketEmitter.sendHelloToEmitter(); + mArduinoSocket.sendHelloToEmitter(); } private void processEBin(String response) { String binFileName = FileUtils.binDir + FileUtils.FILE_NAME_PREFIX + mCurrentRemoteControl.getRemoteMap() + FileUtils.FILE_NAME_EXT; byte []binContent = FileUtils.getByteArrayFromFile(binFileName); - mIRSocketEmitter.sendBinToEmitter(binContent, mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory()); + mArduinoSocket.sendBinToEmitter(binContent, mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory()); } private void processECtrl(String response) { @@ -281,11 +206,11 @@ public class ControlFragment extends Fragment implements View.OnClickListener { private void onEmitterResponse(String response) { Log.d(TAG, "emitter: " + response); - if (response.startsWith(IRSocketEmitter.E_RESPONSE_HELLO)) { + if (response.startsWith(ArduinoSocket.E_RESPONSE_HELLO)) { processEHello(response); - } else if (response.startsWith(IRSocketEmitter.E_RESPONSE_BIN)) { + } else if (response.startsWith(ArduinoSocket.E_RESPONSE_BIN)) { processEBin(response); - } else if (response.startsWith(IRSocketEmitter.E_RESPONSE_CTRL)) { + } else if (response.startsWith(ArduinoSocket.E_RESPONSE_CTRL)) { processECtrl(response); } else { Log.e(TAG, "unexpected response : " + response); @@ -296,63 +221,39 @@ public class ControlFragment extends Fragment implements View.OnClickListener { @Override public void onClick(View v) { vibrate(mParent); - // decode directly in mobile phone - int []decoded = null; + IRemote remote = null; + int keyCode = 0; int id = v.getId(); if (id == R.id.iv_power) { - decoded = irControl(KEY_POWER); + keyCode = IRemote.KEY_POWER; } else if (id == R.id.iv_up) { - decoded = irControl(KEY_UP); + keyCode = IRemote.KEY_UP; } else if (id == R.id.iv_down) { - decoded = irControl(KEY_DOWN); + keyCode = IRemote.KEY_DOWN; } else if (id == R.id.iv_left) { - decoded = irControl(KEY_LEFT); + keyCode = IRemote.KEY_LEFT; } else if (id == R.id.iv_right) { - decoded = irControl(KEY_RIGHT); + keyCode = IRemote.KEY_RIGHT; } else if (id == R.id.iv_ok) { - decoded = irControl(KEY_OK); + keyCode = IRemote.KEY_OK; } else if (id == R.id.iv_plus) { - decoded = irControl(KEY_PLUS); + keyCode = IRemote.KEY_PLUS; } else if (id == R.id.iv_minus) { - decoded = irControl(KEY_MINUS); + keyCode = IRemote.KEY_MINUS; } else if (id == R.id.iv_back) { - decoded = irControl(KEY_BACK); + keyCode = IRemote.KEY_BACK; } else if (id == R.id.iv_home) { - decoded = irControl(KEY_HOME); + keyCode = IRemote.KEY_HOME; } else if (id == R.id.iv_menu) { - decoded = irControl(KEY_MENU); + keyCode = IRemote.KEY_MENU; } - // debug decoded value - StringBuilder decodedValue = new StringBuilder(); - for (int i = 0; i < Objects.requireNonNull(decoded).length; i++) { - decodedValue.append(decoded[i]); - decodedValue.append(","); - } - Log.d(TAG, "decodedValue : " + decodedValue); - if (mIRSocketEmitter.isConnected()) { - Log.d(TAG, "emitter available, send decoded to emitter"); - mIRSocketEmitter.sendDecodedToEmitter(decodedValue.toString()); - } - // send decoded integer array to IR emitter - ConsumerIrManager irEmitter = - (ConsumerIrManager) mParent.getSystemService(Context.CONSUMER_IR_SERVICE); - if (null != irEmitter && irEmitter.hasIrEmitter()) { - if (decoded.length > 0) { - irEmitter.transmit(38000, decoded); - } + if (mArduinoSocket.isConnected()) { + remote = new ArduinoRemote(mParent, mArduinoSocket); } else { - ToastUtils.showToast(mParent, this.getString(R.string.ir_not_supported), null); - } - } - - private boolean isIpAddress(String ipAddress) { - try { - InetAddress inetAddress = InetAddress.getByName(ipAddress); - return Objects.equals(inetAddress.getHostAddress(), ipAddress); - } catch (UnknownHostException e) { - return false; + remote = new PhoneRemote(mParent); } + remote.irControl(mCurrentRemoteControl.getCategoryId(), mCurrentRemoteControl.getSubCategory(), keyCode); } private static class MsgHandler extends Handler { diff --git a/arduino-example/src/configure.h b/arduino-example/src/configure.h index 0d097e6..ea1659b 100644 --- a/arduino-example/src/configure.h +++ b/arduino-example/src/configure.h @@ -27,10 +27,10 @@ #include // Wi-Fi Configs -// #define SECRET_SSID "Maomao的小房子" -// #define SECRET_PASS "Maomao121207" -#define SECRET_SSID "maomao" -#define SECRET_PASS "20121207" +#define SECRET_SSID "Maomao的小房子" +#define SECRET_PASS "Maomao121207" +// #define SECRET_SSID "maomao" +// #define SECRET_PASS "20121207" // LED Matrix Definitions constexpr uint32_t chip[] = {