executeForResults(int expectTaskResultCount) throws RuntimeException;
54 |
55 | boolean isCancelled();
56 | }
57 |
--------------------------------------------------------------------------------
/esptouch-v2/README.md:
--------------------------------------------------------------------------------
1 | # EspTouch V2
2 |
3 |
4 | ## APIs
5 | - Create provisioner instance
6 | ```java
7 | Context context; // Set Application Context
8 | EspProvisioner provisioner = new EspProvisioner(context);
9 | ```
10 |
11 | - Start send Sync packets
12 | ```java
13 | EspSyncListener listener = new EspSyncListener() {
14 | @Override
15 | public void onStart() {
16 | }
17 |
18 | @Override
19 | public void onStop() {
20 | }
21 |
22 | @Override
23 | public void onError(Exception e) {
24 | }
25 | };
26 | provisioner.startSync(listener); // listener is nullable
27 | ```
28 |
29 | - Stop send Sync packets
30 | ```java
31 | provisioner.stopSync();
32 | ```
33 |
34 | - Start provisioning
35 | - Provisioning task will run for 90 seconds
36 | ```java
37 | Context context; // Set Application Context
38 | EspProvisioningRequest request = new EspProvisioningRequest.Builder(context)
39 | .setSSID(ssid) // AP's SSID, nullable
40 | .setBSSID(bssid) // AP's BSSID, nonnull
41 | .setPassword(password) // AP's password, nullable if the AP is open
42 | .setReservedData(customData) // User's custom data, nullable. If not null, the max length is 64
43 | .setAESKey(aesKey) // nullable, if not null, it must be 16 bytes. App developer should negotiate an AES key with Device developer first.
44 | .build();
45 | EspProvisioningListener listener = new EspProvisioningListener() {
46 | @Override
47 | public void onStart() {
48 | }
49 |
50 | @Override
51 | public void onResponse(EspProvisionResult result) {
52 | // Result callback
53 | }
54 |
55 | @Override
56 | public void onStop() {
57 | }
58 |
59 | @Override
60 | public void onError(Exception e) {
61 | }
62 | };
63 | provisioner.startProvisioning(request, listener); // request is nonnull, listener is nullable
64 | ```
65 |
66 | - Stop provisioning
67 | ```java
68 | provisioner.stopProvisioning();
69 | ```
70 |
71 | - Close provisioner instance
72 | - It is necessary to close the provisioner to release the resources
73 | ```java
74 | provisioner.close()
75 | ```
76 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
36 |
41 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/IEsptouchTask.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch;
2 |
3 | import java.util.List;
4 |
5 | public interface IEsptouchTask {
6 | String ESPTOUCH_VERSION = BuildConfig.VERSION_NAME;
7 |
8 | /**
9 | * set the esptouch listener, when one device is connected to the Ap, it will be called back
10 | *
11 | * @param esptouchListener when one device is connected to the Ap, it will be called back
12 | */
13 | void setEsptouchListener(IEsptouchListener esptouchListener);
14 |
15 | /**
16 | * Interrupt the Esptouch Task when User tap back or close the Application.
17 | */
18 | void interrupt();
19 |
20 | /**
21 | * Note: !!!Don't call the task at UI Main Thread or RuntimeException will
22 | * be thrown Execute the Esptouch Task and return the result
23 | *
24 | * Smart Config v2.4 support the API
25 | *
26 | * @return the IEsptouchResult
27 | */
28 | IEsptouchResult executeForResult() throws RuntimeException;
29 |
30 | /**
31 | * Note: !!!Don't call the task at UI Main Thread or RuntimeException will
32 | * be thrown Execute the Esptouch Task and return the result
33 | *
34 | * Smart Config v2.4 support the API
35 | *
36 | * It will be blocked until the client receive result count >= expectTaskResultCount.
37 | * If it fail, it will return one fail result will be returned in the list.
38 | * If it is cancelled while executing,
39 | * if it has received some results, all of them will be returned in the list.
40 | * if it hasn't received any results, one cancel result will be returned in the list.
41 | *
42 | * @param expectTaskResultCount the expect result count(if expectTaskResultCount <= 0,
43 | * expectTaskResultCount = Integer.MAX_VALUE)
44 | * @return the list of IEsptouchResult
45 | */
46 | List executeForResults(int expectTaskResultCount) throws RuntimeException;
47 |
48 | /**
49 | * check whether the task is cancelled by user
50 | *
51 | * @return whether the task is cancelled by user
52 | */
53 | boolean isCancelled();
54 |
55 | /**
56 | * Set broadcast or multicast when post configure info
57 | *
58 | * @param broadcast true is broadcast, false is multicast
59 | */
60 | void setPackageBroadcast(boolean broadcast);
61 | }
62 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/util/TouchNetUtil.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.util;
2 |
3 | import android.content.Context;
4 | import android.net.wifi.WifiInfo;
5 | import android.net.wifi.WifiManager;
6 |
7 | import java.net.InetAddress;
8 | import java.net.UnknownHostException;
9 |
10 | public class TouchNetUtil {
11 |
12 | /**
13 | * get the local ip address by Android System
14 | *
15 | * @param context the context
16 | * @return the local ip addr allocated by Ap
17 | */
18 | public static InetAddress getLocalInetAddress(Context context) {
19 | WifiManager wm = (WifiManager) context.getApplicationContext()
20 | .getSystemService(Context.WIFI_SERVICE);
21 | assert wm != null;
22 | WifiInfo wifiInfo = wm.getConnectionInfo();
23 | int ipAddress = wifiInfo.getIpAddress();
24 | byte[] addressBytes = new byte[]{
25 | (byte) (ipAddress & 0xff),
26 | (byte) (ipAddress >> 8 & 0xff),
27 | (byte) (ipAddress >> 16 & 0xff),
28 | (byte) (ipAddress >> 24 & 0xff)
29 | };
30 | InetAddress localInetAddr = null;
31 | try {
32 | localInetAddr = InetAddress.getByAddress(addressBytes);
33 | } catch (UnknownHostException e) {
34 | e.printStackTrace();
35 | }
36 | return localInetAddr;
37 | }
38 |
39 | /**
40 | * parse InetAddress
41 | *
42 | * @param inetAddrBytes
43 | * @return
44 | */
45 | public static InetAddress parseInetAddr(byte[] inetAddrBytes, int offset, int count) {
46 | InetAddress inetAddress = null;
47 | StringBuilder sb = new StringBuilder();
48 | for (int i = 0; i < count; i++) {
49 | sb.append((inetAddrBytes[offset + i] & 0xff));
50 | if (i != count - 1) {
51 | sb.append('.');
52 | }
53 | }
54 | try {
55 | inetAddress = InetAddress.getByName(sb.toString());
56 | } catch (UnknownHostException e) {
57 | e.printStackTrace();
58 | }
59 | return inetAddress;
60 | }
61 |
62 | /**
63 | * parse bssid
64 | *
65 | * @param bssid the bssid like aa:bb:cc:dd:ee:ff
66 | * @return byte converted from bssid
67 | */
68 | public static byte[] parseBssid2bytes(String bssid) {
69 | String[] bssidSplits = bssid.split(":");
70 | byte[] result = new byte[bssidSplits.length];
71 | for (int i = 0; i < bssidSplits.length; i++) {
72 | result[i] = (byte) Integer.parseInt(bssidSplits[i], 16);
73 | }
74 | return result;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/espressif/esptouch/android/EspTouchApp.java:
--------------------------------------------------------------------------------
1 | package com.espressif.esptouch.android;
2 |
3 | import android.app.Application;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 | import android.location.LocationManager;
9 | import android.net.wifi.WifiManager;
10 | import android.os.Build;
11 |
12 | import androidx.lifecycle.LifecycleOwner;
13 | import androidx.lifecycle.MutableLiveData;
14 | import androidx.lifecycle.Observer;
15 |
16 | import java.util.HashMap;
17 | import java.util.Map;
18 |
19 | public class EspTouchApp extends Application {
20 | private static EspTouchApp app;
21 |
22 | private MutableLiveData mBroadcastData;
23 |
24 | private Map mCacheMap;
25 |
26 | private BroadcastReceiver mReceiver = new BroadcastReceiver() {
27 | @Override
28 | public void onReceive(Context context, Intent intent) {
29 | String action = intent.getAction();
30 | if (action == null) {
31 | return;
32 | }
33 |
34 | switch (action) {
35 | case WifiManager.NETWORK_STATE_CHANGED_ACTION:
36 | case LocationManager.PROVIDERS_CHANGED_ACTION:
37 | mBroadcastData.setValue(action);
38 | break;
39 | }
40 | }
41 | };
42 |
43 | @Override
44 | public void onCreate() {
45 | super.onCreate();
46 |
47 | app = this;
48 |
49 | mCacheMap = new HashMap<>();
50 |
51 | mBroadcastData = new MutableLiveData<>();
52 | IntentFilter filter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
53 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
54 | filter.addAction(LocationManager.PROVIDERS_CHANGED_ACTION);
55 | }
56 | registerReceiver(mReceiver, filter);
57 | }
58 |
59 | @Override
60 | public void onTerminate() {
61 | super.onTerminate();
62 |
63 | unregisterReceiver(mReceiver);
64 | }
65 |
66 | public static EspTouchApp getInstance() {
67 | return app;
68 | }
69 |
70 | public void observeBroadcast(LifecycleOwner owner, Observer observer) {
71 | mBroadcastData.observe(owner, observer);
72 | }
73 |
74 | public void observeBroadcastForever(Observer observer) {
75 | mBroadcastData.observeForever(observer);
76 | }
77 |
78 | public void removeBroadcastObserver(Observer observer) {
79 | mBroadcastData.removeObserver(observer);
80 | }
81 |
82 | public void putCache(String key, Object value) {
83 | mCacheMap.put(key, value);
84 | }
85 |
86 | public Object takeCache(String key) {
87 | return mCacheMap.remove(key);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/espressif/esptouch/android/main/EspMainActivity.java:
--------------------------------------------------------------------------------
1 | package com.espressif.esptouch.android.main;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 | import androidx.appcompat.app.AppCompatActivity;
12 | import androidx.recyclerview.widget.DividerItemDecoration;
13 | import androidx.recyclerview.widget.OrientationHelper;
14 | import androidx.recyclerview.widget.RecyclerView;
15 |
16 | import com.espressif.esptouch.android.R;
17 | import com.espressif.esptouch.android.v1.EspTouchActivity;
18 | import com.espressif.esptouch.android.v2.EspTouch2Activity;
19 |
20 | public class EspMainActivity extends AppCompatActivity {
21 | private static final String[] ITEMS = {
22 | "EspTouch",
23 | "EspTouch V2"
24 | };
25 |
26 | @Override
27 | protected void onCreate(@Nullable Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_main);
30 | setTitle(R.string.main_title);
31 |
32 | RecyclerView recyclerView = findViewById(R.id.recyclerView);
33 | recyclerView.addItemDecoration(new DividerItemDecoration(this, OrientationHelper.VERTICAL));
34 | recyclerView.setAdapter(new ItemAdapter());
35 | }
36 |
37 | private class ItemHolder extends RecyclerView.ViewHolder {
38 | int position;
39 | TextView label;
40 |
41 | ItemHolder(@NonNull View itemView) {
42 | super(itemView);
43 |
44 | label = itemView.findViewById(R.id.label);
45 |
46 | itemView.setOnClickListener(v -> {
47 | switch (position) {
48 | case 0:
49 | startActivity(new Intent(EspMainActivity.this, EspTouchActivity.class));
50 | break;
51 | case 1:
52 | startActivity(new Intent(EspMainActivity.this, EspTouch2Activity.class));
53 | break;
54 | }
55 | });
56 | }
57 | }
58 |
59 | private class ItemAdapter extends RecyclerView.Adapter {
60 |
61 | @NonNull
62 | @Override
63 | public ItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
64 | View itemView = getLayoutInflater().inflate(R.layout.activity_main_item, parent, false);
65 | return new ItemHolder(itemView);
66 | }
67 |
68 | @Override
69 | public void onBindViewHolder(@NonNull ItemHolder holder, int position) {
70 | String item = ITEMS[position];
71 | holder.position = position;
72 | holder.label.setText(item);
73 | }
74 |
75 | @Override
76 | public int getItemCount() {
77 | return ITEMS.length;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/protocol/DataCode.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.protocol;
2 |
3 | import com.espressif.iot.esptouch.task.ICodeData;
4 | import com.espressif.iot.esptouch.util.ByteUtil;
5 | import com.espressif.iot.esptouch.util.CRC8;
6 |
7 | /**
8 | * one data format:(data code should have 2 to 65 data)
9 | *
10 | * control byte high 4 bits low 4 bits
11 | * 1st 9bits: 0x0 crc(high) data(high)
12 | * 2nd 9bits: 0x1 sequence header
13 | * 3rd 9bits: 0x0 crc(low) data(low)
14 | *
15 | * sequence header: 0,1,2,...
16 | *
17 | * @author afunx
18 | */
19 | public class DataCode implements ICodeData {
20 |
21 | public static final int DATA_CODE_LEN = 6;
22 |
23 | private static final int INDEX_MAX = 127;
24 |
25 | private final byte mSeqHeader;
26 | private final byte mDataHigh;
27 | private final byte mDataLow;
28 | // the crc here means the crc of the data and sequence header be transformed
29 | // it is calculated by index and data to be transformed
30 | private final byte mCrcHigh;
31 | private final byte mCrcLow;
32 |
33 | /**
34 | * Constructor of DataCode
35 | *
36 | * @param u8 the character to be transformed
37 | * @param index the index of the char
38 | */
39 | public DataCode(char u8, int index) {
40 | if (index > INDEX_MAX) {
41 | throw new RuntimeException("index > INDEX_MAX");
42 | }
43 | byte[] dataBytes = ByteUtil.splitUint8To2bytes(u8);
44 | mDataHigh = dataBytes[0];
45 | mDataLow = dataBytes[1];
46 | CRC8 crc8 = new CRC8();
47 | crc8.update(ByteUtil.convertUint8toByte(u8));
48 | crc8.update(index);
49 | byte[] crcBytes = ByteUtil.splitUint8To2bytes((char) crc8.getValue());
50 | mCrcHigh = crcBytes[0];
51 | mCrcLow = crcBytes[1];
52 | mSeqHeader = (byte) index;
53 | }
54 |
55 | @Override
56 | public byte[] getBytes() {
57 | byte[] dataBytes = new byte[DATA_CODE_LEN];
58 | dataBytes[0] = 0x00;
59 | dataBytes[1] = ByteUtil.combine2bytesToOne(mCrcHigh, mDataHigh);
60 | dataBytes[2] = 0x01;
61 | dataBytes[3] = mSeqHeader;
62 | dataBytes[4] = 0x00;
63 | dataBytes[5] = ByteUtil.combine2bytesToOne(mCrcLow, mDataLow);
64 | return dataBytes;
65 | }
66 |
67 | @Override
68 | public String toString() {
69 | StringBuilder sb = new StringBuilder();
70 | byte[] dataBytes = getBytes();
71 | for (int i = 0; i < DATA_CODE_LEN; i++) {
72 | String hexString = ByteUtil.convertByte2HexString(dataBytes[i]);
73 | sb.append("0x");
74 | if (hexString.length() == 1) {
75 | sb.append("0");
76 | }
77 | sb.append(hexString).append(" ");
78 | }
79 | return sb.toString();
80 | }
81 |
82 | @Override
83 | public char[] getU8s() {
84 | throw new RuntimeException("DataCode don't support getU8s()");
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/esptouch-v2/src/main/java/com/espressif/iot/esptouch2/provision/TouchAES.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch2.provision;
2 |
3 | import android.util.Log;
4 |
5 | import java.security.InvalidAlgorithmParameterException;
6 | import java.security.InvalidKeyException;
7 | import java.security.NoSuchAlgorithmException;
8 |
9 | import javax.crypto.BadPaddingException;
10 | import javax.crypto.Cipher;
11 | import javax.crypto.IllegalBlockSizeException;
12 | import javax.crypto.NoSuchPaddingException;
13 | import javax.crypto.spec.IvParameterSpec;
14 | import javax.crypto.spec.SecretKeySpec;
15 |
16 | class TouchAES {
17 | private static final String TAG = "TouchAES";
18 |
19 | private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
20 |
21 | private final byte[] mKey;
22 | private final byte[] mIV;
23 | private final Cipher mEncryptCipher;
24 | private final Cipher mDecryptCipher;
25 |
26 | TouchAES(byte[] key) {
27 | this(key, null);
28 | }
29 |
30 | TouchAES(byte[] key, byte[] iv) {
31 | mKey = key;
32 |
33 | mIV = new byte[16];
34 | if (iv != null) {
35 | System.arraycopy(iv, 0, mIV, 0, Math.min(iv.length, mIV.length));
36 | }
37 |
38 | mEncryptCipher = createEncryptCipher();
39 | mDecryptCipher = createDecryptCipher();
40 | }
41 |
42 | private Cipher createEncryptCipher() {
43 | try {
44 | Cipher cipher = Cipher.getInstance(TRANSFORMATION);
45 |
46 | SecretKeySpec secretKeySpec = new SecretKeySpec(mKey, "AES");
47 | IvParameterSpec parameterSpec = new IvParameterSpec(mIV);
48 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, parameterSpec);
49 |
50 | return cipher;
51 | } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException
52 | e) {
53 | Log.w(TAG, "createEncryptCipher: ", e);
54 | }
55 |
56 | return null;
57 | }
58 |
59 | private Cipher createDecryptCipher() {
60 | try {
61 | Cipher cipher = Cipher.getInstance(TRANSFORMATION);
62 |
63 | SecretKeySpec secretKeySpec = new SecretKeySpec(mKey, "AES");
64 | IvParameterSpec parameterSpec = new IvParameterSpec(mIV);
65 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, parameterSpec);
66 |
67 | return cipher;
68 | } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException
69 | e) {
70 | Log.w(TAG, "createDecryptCipher: ", e);
71 | }
72 |
73 | return null;
74 | }
75 |
76 | byte[] encrypt(byte[] content) {
77 | try {
78 | return mEncryptCipher.doFinal(content);
79 | } catch (BadPaddingException | IllegalBlockSizeException e) {
80 | Log.w(TAG, "encrypt: ", e);
81 | }
82 | return null;
83 | }
84 |
85 | byte[] decrypt(byte[] content) {
86 | try {
87 | return mDecryptCipher.doFinal(content);
88 | } catch (BadPaddingException | IllegalBlockSizeException e) {
89 | Log.w(TAG, "decrypt: ", e);
90 | }
91 |
92 | return null;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | EspTouch
4 | 关于 App
5 | App 版本: %s
6 |
7 | 主页
8 |
9 | 需要位置权限来获取 Wi-Fi 信息。 \n点击申请权限
10 | 请打开 GPS 以获取 Wi-Fi 信息。
11 | 请先连上 Wi-Fi
12 | 当前连接的是 5G Wi-Fi, 请确定您的设备是否支持。
13 |
14 | EspTouch
15 | EspTouch 版本: %s
16 | SSID:
17 | BSSID:
18 | 密码:
19 | 设备数量:
20 | 广播
21 | 组播
22 | 确认
23 | ⚠️警告️
24 | 在 Android M 及以上版本,如果您禁止授权位置权限,APP将无法获取 Wi-Fi 信息。
25 | Wi-Fi 已断开或发生了变化
26 | Esptouch 正在执行配网, 请稍等片刻…
27 | EspTouch 配网失败
28 | 建立 EspTouch 任务失败, 端口可能被其他程序占用
29 | EspTouch 完成
30 | BSSID: %1$s, 地址: %2$s
31 |
32 | EspTouch V2
33 | EspTouch V2 版本: %s
34 | SSID:
35 | BSSID:
36 | IP:
37 | 密码
38 | AES 密钥
39 | AES 密钥必须为空或者16字节数据
40 | 加密:
41 | 模式 1
42 | 模式 2
43 | 自定义数据
44 | 自定义数据不能超过%d字节
45 | 需要配网的设备数量
46 |
47 | 配网
48 | 停止
49 | 捕获到错误:%s
50 | 没有找到设备
51 | Wi-Fi 连接已断开
52 | BSSID: %1$s
53 | 地址: %1$s
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | EspTouch
4 | About App
5 | App Version: %s
6 |
7 | Home
8 |
9 | APP require Location permission to get Wi-Fi information. \nClick to request permission
10 | Please turn on GPS to get Wi-Fi information.
11 | Please connect Wi-Fi first.
12 | Current Wi-Fi connection is 5G, make sure your device supports it.
13 |
14 | EspTouch
15 | EspTouch Version: %s
16 | SSID:
17 | BSSID:
18 | Password:
19 | Device count:
20 | Broadcast
21 | Multicast
22 | Confirm
23 | ⚠️Warning
24 | On Android M or higher version, App can\'t get Wi-Fi connection information if you forbid Location permission.
25 | Esptouch is configuring, please wait for a moment…
26 | Wi-Fi disconnected or changed
27 | EspTouch failed
28 | Create EspTouch task failed, the EspTouch port could be used by other thread
29 | EspTouch 完成
30 | BSSID = %1$s, InetAddress = %2$s
31 |
32 | EspTouch V2
33 | EspTouch V2 Version: %s
34 | SSID:
35 | BSSID:
36 | IP:
37 | Password
38 | AES Key
39 | AES Key must be null or 16 bytes data
40 | Security ver:
41 | mode 1
42 | mode 2
43 | Custom Data
44 | Custom data can\'t be more than %d bytes
45 | Device Count for Provisioning
46 |
47 | Provision
48 | Stop
49 | Catch exception: %s
50 | Not found any devices
51 | Wi-Fi connection is disconnected
52 | BSSID: %1$s
53 | Address: %1$s
54 |
55 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/security/TouchAES.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.security;
2 |
3 | import java.security.InvalidAlgorithmParameterException;
4 | import java.security.InvalidKeyException;
5 | import java.security.NoSuchAlgorithmException;
6 |
7 | import javax.crypto.BadPaddingException;
8 | import javax.crypto.Cipher;
9 | import javax.crypto.IllegalBlockSizeException;
10 | import javax.crypto.NoSuchPaddingException;
11 | import javax.crypto.spec.IvParameterSpec;
12 | import javax.crypto.spec.SecretKeySpec;
13 |
14 | public class TouchAES implements ITouchEncryptor {
15 | private static final String TRANSFORMATION_DEFAULT = "AES/ECB/PKCS5Padding";
16 |
17 | private final byte[] mKey;
18 | private final byte[] mIV;
19 | private final String mTransformation;
20 | private Cipher mEncryptCipher;
21 | private Cipher mDecryptCipher;
22 |
23 | public TouchAES(byte[] key) {
24 | this(key, null, TRANSFORMATION_DEFAULT);
25 | }
26 |
27 | public TouchAES(byte[] key, String transformation) {
28 | this(key, null, transformation);
29 | }
30 |
31 | public TouchAES(byte[] key, byte[] iv) {
32 | this(key, iv, TRANSFORMATION_DEFAULT);
33 | }
34 |
35 | public TouchAES(byte[] key, byte[] iv, String transformation) {
36 | mKey = key;
37 | mIV = iv;
38 | mTransformation = transformation;
39 |
40 | mEncryptCipher = createEncryptCipher();
41 | mDecryptCipher = createDecryptCipher();
42 | }
43 |
44 | private Cipher createEncryptCipher() {
45 | try {
46 | Cipher cipher = Cipher.getInstance(mTransformation);
47 |
48 | SecretKeySpec secretKeySpec = new SecretKeySpec(mKey, "AES");
49 | if (mIV == null) {
50 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
51 | } else {
52 | IvParameterSpec parameterSpec = new IvParameterSpec(mIV);
53 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, parameterSpec);
54 | }
55 |
56 | return cipher;
57 | } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException
58 | e) {
59 | e.printStackTrace();
60 | }
61 |
62 | return null;
63 | }
64 |
65 | private Cipher createDecryptCipher() {
66 | try {
67 | Cipher cipher = Cipher.getInstance(mTransformation);
68 |
69 | SecretKeySpec secretKeySpec = new SecretKeySpec(mKey, "AES");
70 | if (mIV == null) {
71 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
72 | } else {
73 | IvParameterSpec parameterSpec = new IvParameterSpec(mIV);
74 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, parameterSpec);
75 | }
76 |
77 | return cipher;
78 | } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException
79 | e) {
80 | e.printStackTrace();
81 | }
82 |
83 | return null;
84 | }
85 |
86 | public byte[] encrypt(byte[] content) {
87 | try {
88 | return mEncryptCipher.doFinal(content);
89 | } catch (BadPaddingException | IllegalBlockSizeException e) {
90 | e.printStackTrace();
91 | }
92 | return null;
93 | }
94 |
95 | public byte[] decrypt(byte[] content) {
96 | try {
97 | return mDecryptCipher.doFinal(content);
98 | } catch (BadPaddingException | IllegalBlockSizeException e) {
99 | e.printStackTrace();
100 | }
101 |
102 | return null;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/docs/esptouch-user-guide-cn.md:
--------------------------------------------------------------------------------
1 | [[English]](esptouch-user-guide-en.md)
2 |
3 | # ESP-TOUCH 用户指南
4 |
5 | ---
6 |
7 | ## 技术原理
8 |
9 | 乐鑫自主研发的 ESP-TOUCH 协议采⽤的是 Smart Config(智能配置)技术,帮助用户将采用 ESP8266 和 ESP32 的设备(以下简称“设备”)连接至 Wi-Fi 网络。用户只需在⼿机上进行简单操作即可实现智能配置。
10 |
11 |
12 |
13 | 由于设备一开始尚未联网,ESP-TOUCH 应用无法直接向设备发送信息。通过 ESP-TOUCH 通信协议,具有 Wi-Fi 网络接入能力的设备(例如智能手机)可以向 Wi-Fi 接入点 (AP) 发送一系列 UDP 数据包,其中每一包的长度(即 Length 字段)都按照 ESP- TOUCH 通信协议进⾏编码,SSID 和密码就包含在 Length 字段中,随后设备便能够接收这些 UDP 数据包,并从中解析出所需的信息。
14 |
15 | 数据包结构如下表所示:
16 |
17 | | 字段 | 长度(字节) | 描述 |
18 | |--------|----------------|-------------------------------------|
19 | | DA | 6 | 目标 MAC 地址 |
20 | | SA | 6 | 源 MAC 地址 |
21 | | Length | 2 | 包含 SSID 和密钥 |
22 | | LLC | 3 | 逻辑链路控制 |
23 | | SNAP | 5 | 子网接入协议 |
24 | | DATA | Variable | 载荷 |
25 | | FCS | 4 | 帧校验序列 |
26 |
27 |
28 | ## ESP-TOUCH 操作指南
29 |
30 | ### 功能概述
31 |
32 | - **支持的平台**:
33 | - ESP8266:OS SDK 和 NonOS SDK
34 | - ESP32:ESP-IDF
35 | - **协议兼容性**:集成了微信 AirKiss 协议
36 | - 用户可以通过 ESP-TOUCH 应用或微信客户端配置设备
37 |
38 | ### 操作流程
39 |
40 | 1. 准备一台支持 ESP-TOUCH 的设备,开启 Smart Config 功能。
41 | 2. 将手机连接至目标路由器。
42 | 3. 打开手机上的 ESP-TOUCH 应用。
43 | 4. 输入路由器的 SSID 和密码以将设备连接到路由器。如果路由器是开放网络(未加密),则密码字段可留空。
44 |
45 | **重要注意事项**
46 |
47 | - **距离影响**:设备与路由器距离越远,连接耗时越长
48 | - **路由器状态**:配置前需确保路由器已启动
49 | - **超时机制**:
50 | - 应用端超时未连接会返回配置失败信息
51 | - 设备端超时未获取信息会自动重启 Smart Config
52 | - 可通过 `esptouch_set_timeout(uint8 time_s)` 或 `esp_smartconfig_set_timeout(uint8 time_s)` 设置超时时间
53 | - **工作模式**:
54 | - **ESP8266**:需启用 Sniffer 模式,禁用 Station/soft-AP 模式
55 | - **ESP32**:可同时启用 Sniffer 和 Station 模式
56 | - **IP 交互**:配置完成后,手机端获取设备 IP,设备端返回手机 IP,支持自定义局域网通信
57 | - **AP 隔离**:路由器启用 AP 隔离可能导致应用无法收到成功提示
58 | - **多设备支持**:应用可同时配置多个设备接入同一路由器
59 | - **网络限制**:不支持 5 GHz 频段和 802.11ac 协议
60 |
61 | ## 性能分析
62 |
63 | ESP-TOUCH 的通信模型可以抽象为某种错误率的单向通道,但这种错误率又根据带宽的不同而有所不同。通常:
64 |
65 | - 20 MHz 带宽下,数据包错误率约为 0–5%
66 | - 40 MHz 带宽下,数据包错误率约为 0–17%
67 |
68 | 假设所需要传递信息的最大长度为 104 字节,在这种情况下,若不采用纠错算法,则难以保证在有限的传递次数内完成信息的发送。
69 |
70 | ### 累积纠错算法
71 |
72 | 为了解决这一问题,ESP-TOUCH 采用了累积纠错算法,以确保在有限的次数内完成信息发送。该算法的理论基础是:在多轮数据发送过程中,同一位数据出错的概率非常低。因此可以累积多轮数据传递结果进行分析,在某一轮中发生错误的数据位可能在其他轮中找到其对应的正确值,从而保证在有限的次数内完成信息的发送。
73 |
74 | 信息发送的成功率可以通过以下公式表示:
75 |
76 | 成功率 = [1 – (1 – P)k ]l
77 |
78 | 其中:
79 | - P:单个数据包的成功率
80 | - k:发送轮次
81 | - l:发送信息的长度(字节)
82 |
83 | **典型场景分析**:
84 | - **20 MHz 带宽** (*P = 0.95*):104 字节数据成功率可达 95%
85 | - **40 MHz 带宽** (*P = 0.83*):72 字节数据成功率可达 83%
86 |
87 | 以下表格显示了采用累积纠错算法时,信息发送的成功率和发送时间的不同情况。
88 |
89 | **表:20 MHz 带宽分析**
90 |
91 | | 轮次 | 时间(秒) – 104 字节 | 成功率 – 104 字节 | 时间(秒) – 72 字节 | 成功率 – 72 字节 |
92 | |------|----------------------|-------------------|---------------------|------------------|
93 | | 1 | 4.68 | 0.0048 | 3.24 | 0.0249 |
94 | | 2 | 9.36 | 0.771 | 6.48 | 0.835 |
95 | | 3 | 14.04 | 0.987 | 9.72 | 0.991 |
96 | | 4 | 18.72 | 0.9994 | 12.90 | 0.9996 |
97 | | 5 | 23.40 | 0.99997 | 16.20 | 0.99998 |
98 | | 6 | 28.08 | 0.999998 | 19.40 | 0.99999 |
99 |
100 | **表:40 MHz 带宽分析**
101 |
102 | | 轮次 | 时间(秒) – 104 字节 | 成功率 – 104 字节 | 时间(秒) – 72 字节 | 成功率 – 72 字节 |
103 | |------|----------------------|-------------------|---------------------|------------------|
104 | | 1 | 4.68 | 3.84e-9 | 3.24 | 1.49e-6 |
105 | | 2 | 9.36 | 0.0474 | 6.48 | 0.121 |
106 | | 3 | 14.04 | 0.599 | 9.72 | 0.701 |
107 | | 4 | 18.72 | 0.917 | 12.90 | 0.942 |
108 | | 5 | 23.40 | 0.985 | 16.20 | 0.989 |
109 | | 6 | 28.08 | 0.997 | 19.40 | 0.998
110 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/EsptouchTask.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 |
6 | import com.espressif.iot.esptouch.protocol.TouchData;
7 | import com.espressif.iot.esptouch.security.ITouchEncryptor;
8 | import com.espressif.iot.esptouch.task.EsptouchTaskParameter;
9 | import com.espressif.iot.esptouch.task.__EsptouchTask;
10 | import com.espressif.iot.esptouch.util.TouchNetUtil;
11 |
12 | import java.util.List;
13 |
14 | public class EsptouchTask implements IEsptouchTask {
15 | private __EsptouchTask _mEsptouchTask;
16 | private EsptouchTaskParameter _mParameter;
17 |
18 | /**
19 | * Constructor of EsptouchTask
20 | *
21 | * @param apSsid the Ap's ssid
22 | * @param apBssid the Ap's bssid, like aa:bb:cc:dd:ee:ff
23 | * @param apPassword the Ap's password
24 | * @param context the {@link Context} of the Application
25 | */
26 | public EsptouchTask(String apSsid, String apBssid, String apPassword, Context context) {
27 | this(apSsid, apBssid, apPassword, null, context);
28 | }
29 |
30 | /**
31 | * Constructor of EsptouchTask
32 | *
33 | * @param apSsid the Ap's ssid
34 | * @param apBssid the Ap's bssid, the length must be 6
35 | * @param apPassword the Ap's password
36 | * @param context the {@link Context} of the Application
37 | */
38 | public EsptouchTask(byte[] apSsid, byte[] apBssid, byte[] apPassword, Context context) {
39 | this(apSsid, apBssid, apPassword, null, context);
40 | }
41 |
42 | private EsptouchTask(String apSsid, String apBssid, String apPassword, ITouchEncryptor encryptor, Context context) {
43 | if (TextUtils.isEmpty(apSsid)) {
44 | throw new NullPointerException("SSID can't be empty");
45 | }
46 | if (TextUtils.isEmpty(apBssid)) {
47 | throw new NullPointerException("BSSID can't be empty");
48 | }
49 | if (apPassword == null) {
50 | apPassword = "";
51 | }
52 | TouchData ssid = new TouchData(apSsid);
53 | TouchData bssid = new TouchData(TouchNetUtil.parseBssid2bytes(apBssid));
54 | if (bssid.getData().length != 6) {
55 | throw new IllegalArgumentException("Bssid format must be aa:bb:cc:dd:ee:ff");
56 | }
57 | TouchData password = new TouchData(apPassword);
58 | init(context, ssid, bssid, password, encryptor);
59 | }
60 |
61 | private EsptouchTask(byte[] apSsid, byte[] apBssid, byte[] apPassword, ITouchEncryptor encryptor, Context context) {
62 | if (apSsid == null || apSsid.length == 0) {
63 | throw new NullPointerException("SSID can't be empty");
64 | }
65 | if (apBssid == null || apBssid.length != 6) {
66 | throw new NullPointerException("BSSID is empty or length is not 6");
67 | }
68 | if (apPassword == null) {
69 | apPassword = new byte[0];
70 | }
71 | TouchData ssid = new TouchData(apSsid);
72 | TouchData bssid = new TouchData(apBssid);
73 | TouchData password = new TouchData(apPassword);
74 | init(context, ssid, bssid, password, encryptor);
75 | }
76 |
77 | private void init(Context context, TouchData ssid, TouchData bssid, TouchData password, ITouchEncryptor encryptor) {
78 | _mParameter = new EsptouchTaskParameter();
79 | _mEsptouchTask = new __EsptouchTask(context, ssid, bssid, password, encryptor, _mParameter);
80 | }
81 |
82 | @Override
83 | public void interrupt() {
84 | _mEsptouchTask.interrupt();
85 | }
86 |
87 | @Override
88 | public IEsptouchResult executeForResult() throws RuntimeException {
89 | return _mEsptouchTask.executeForResult();
90 | }
91 |
92 | @Override
93 | public boolean isCancelled() {
94 | return _mEsptouchTask.isCancelled();
95 | }
96 |
97 | @Override
98 | public List executeForResults(int expectTaskResultCount)
99 | throws RuntimeException {
100 | if (expectTaskResultCount <= 0) {
101 | expectTaskResultCount = Integer.MAX_VALUE;
102 | }
103 | return _mEsptouchTask.executeForResults(expectTaskResultCount);
104 | }
105 |
106 | @Override
107 | public void setEsptouchListener(IEsptouchListener esptouchListener) {
108 | _mEsptouchTask.setEsptouchListener(esptouchListener);
109 | }
110 |
111 | @Override
112 | public void setPackageBroadcast(boolean broadcast) {
113 | _mParameter.setBroadcast(broadcast);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/udp/UDPSocketClient.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.udp;
2 |
3 | import android.util.Log;
4 |
5 | import com.espressif.iot.esptouch.task.__IEsptouchTask;
6 |
7 | import java.io.IOException;
8 | import java.net.DatagramPacket;
9 | import java.net.DatagramSocket;
10 | import java.net.InetAddress;
11 | import java.net.SocketException;
12 | import java.net.UnknownHostException;
13 |
14 | /**
15 | * this class is used to help send UDP data according to length
16 | *
17 | * @author afunx
18 | */
19 | public class UDPSocketClient {
20 |
21 | private static final String TAG = "UDPSocketClient";
22 | private DatagramSocket mSocket;
23 | private volatile boolean mIsStop;
24 | private volatile boolean mIsClosed;
25 |
26 | public UDPSocketClient() {
27 | try {
28 | this.mSocket = new DatagramSocket();
29 | this.mIsStop = false;
30 | this.mIsClosed = false;
31 | } catch (SocketException e) {
32 | if (__IEsptouchTask.DEBUG) {
33 | Log.w(TAG, "SocketException");
34 | }
35 | e.printStackTrace();
36 | }
37 | }
38 |
39 | @Override
40 | protected void finalize() throws Throwable {
41 | close();
42 | super.finalize();
43 | }
44 |
45 | public void interrupt() {
46 | if (__IEsptouchTask.DEBUG) {
47 | Log.i(TAG, "USPSocketClient is interrupt");
48 | }
49 | this.mIsStop = true;
50 | }
51 |
52 | /**
53 | * close the UDP socket
54 | */
55 | public synchronized void close() {
56 | if (!this.mIsClosed) {
57 | this.mSocket.close();
58 | this.mIsClosed = true;
59 | }
60 | }
61 |
62 | /**
63 | * send the data by UDP
64 | *
65 | * @param data the data to be sent
66 | * @param targetPort the port of target
67 | * @param interval the milliseconds to between each UDP sent
68 | */
69 | public void sendData(byte[][] data, String targetHostName, int targetPort,
70 | long interval) {
71 | sendData(data, 0, data.length, targetHostName, targetPort, interval);
72 | }
73 |
74 |
75 | /**
76 | * send the data by UDP
77 | *
78 | * @param data the data to be sent
79 | * @param offset the offset which data to be sent
80 | * @param count the count of the data
81 | * @param targetPort the port of target
82 | * @param interval the milliseconds to between each UDP sent
83 | */
84 | public void sendData(byte[][] data, int offset, int count,
85 | String targetHostName, int targetPort, long interval) {
86 | if ((data == null) || (data.length <= 0)) {
87 | if (__IEsptouchTask.DEBUG) {
88 | Log.w(TAG, "sendData(): data == null or length <= 0");
89 | }
90 | return;
91 | }
92 | for (int i = offset; !mIsStop && i < offset + count; i++) {
93 | if (data[i].length == 0) {
94 | continue;
95 | }
96 | try {
97 | InetAddress targetInetAddress = InetAddress.getByName(targetHostName);
98 | DatagramPacket localDatagramPacket = new DatagramPacket(
99 | data[i], data[i].length, targetInetAddress, targetPort);
100 | this.mSocket.send(localDatagramPacket);
101 | } catch (UnknownHostException e) {
102 | if (__IEsptouchTask.DEBUG) {
103 | Log.w(TAG, "sendData(): UnknownHostException");
104 | }
105 | e.printStackTrace();
106 | mIsStop = true;
107 | break;
108 | } catch (IOException e) {
109 | if (__IEsptouchTask.DEBUG) {
110 | Log.w(TAG, "sendData(): IOException, but just ignore it");
111 | }
112 | // for the Ap will make some troubles when the phone send too many UDP packets,
113 | // but we don't expect the UDP packet received by others, so just ignore it
114 | }
115 | try {
116 | Thread.sleep(interval);
117 | } catch (InterruptedException e) {
118 | e.printStackTrace();
119 | if (__IEsptouchTask.DEBUG) {
120 | Log.w(TAG, "sendData is Interrupted");
121 | }
122 | mIsStop = true;
123 | break;
124 | }
125 | }
126 | if (mIsStop) {
127 | close();
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/task/IEsptouchTaskParameter.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.task;
2 |
3 | public interface IEsptouchTaskParameter {
4 |
5 | /**
6 | * get interval millisecond for guide code(the time between each guide code sending)
7 | *
8 | * @return interval millisecond for guide code(the time between each guide code sending)
9 | */
10 | long getIntervalGuideCodeMillisecond();
11 |
12 | /**
13 | * get interval millisecond for data code(the time between each data code sending)
14 | *
15 | * @return interval millisecond for data code(the time between each data code sending)
16 | */
17 | long getIntervalDataCodeMillisecond();
18 |
19 | /**
20 | * get timeout millisecond for guide code(the time how much the guide code sending)
21 | *
22 | * @return timeout millisecond for guide code(the time how much the guide code sending)
23 | */
24 | long getTimeoutGuideCodeMillisecond();
25 |
26 | /**
27 | * get timeout millisecond for data code(the time how much the data code sending)
28 | *
29 | * @return timeout millisecond for data code(the time how much the data code sending)
30 | */
31 | long getTimeoutDataCodeMillisecond();
32 |
33 | /**
34 | * get timeout millisecond for total code(guide code and data code altogether)
35 | *
36 | * @return timeout millisecond for total code(guide code and data code altogether)
37 | */
38 | long getTimeoutTotalCodeMillisecond();
39 |
40 | /**
41 | * get total repeat time for executing esptouch task
42 | *
43 | * @return total repeat time for executing esptouch task
44 | */
45 | int getTotalRepeatTime();
46 |
47 | /**
48 | * the length of the Esptouch result 1st byte is the total length of ssid and
49 | * password, the other 6 bytes are the device's bssid
50 | */
51 |
52 | /**
53 | * get esptouchResult length of one
54 | *
55 | * @return length of one
56 | */
57 | int getEsptouchResultOneLen();
58 |
59 | /**
60 | * get esptouchResult length of mac
61 | *
62 | * @return length of mac
63 | */
64 | int getEsptouchResultMacLen();
65 |
66 | /**
67 | * get esptouchResult length of ip
68 | *
69 | * @return length of ip
70 | */
71 | int getEsptouchResultIpLen();
72 |
73 | /**
74 | * get esptouchResult total length
75 | *
76 | * @return total length
77 | */
78 | int getEsptouchResultTotalLen();
79 |
80 | /**
81 | * get port for listening(used by server)
82 | *
83 | * @return port for listening(used by server)
84 | */
85 | int getPortListening();
86 |
87 | /**
88 | * get target hostname
89 | *
90 | * @return target hostame(used by client)
91 | */
92 | String getTargetHostname();
93 |
94 | /**
95 | * get target port
96 | *
97 | * @return target port(used by client)
98 | */
99 | int getTargetPort();
100 |
101 | /**
102 | * get millisecond for waiting udp receiving(receiving without sending)
103 | *
104 | * @return millisecond for waiting udp receiving(receiving without sending)
105 | */
106 | int getWaitUdpReceivingMillisecond();
107 |
108 | /**
109 | * get millisecond for waiting udp sending(sending including receiving)
110 | *
111 | * @return millisecond for waiting udep sending(sending including receiving)
112 | */
113 | int getWaitUdpSendingMillisecond();
114 |
115 | /**
116 | * get millisecond for waiting udp sending and receiving
117 | *
118 | * @return millisecond for waiting udp sending and receiving
119 | */
120 | int getWaitUdpTotalMillisecond();
121 |
122 | /**
123 | * set the millisecond for waiting udp sending and receiving
124 | *
125 | * @param waitUdpTotalMillisecond the millisecond for waiting udp sending and receiving
126 | */
127 | void setWaitUdpTotalMillisecond(int waitUdpTotalMillisecond);
128 |
129 | /**
130 | * get the threshold for how many correct broadcast should be received
131 | *
132 | * @return the threshold for how many correct broadcast should be received
133 | */
134 | int getThresholdSucBroadcastCount();
135 |
136 | /**
137 | * get the count of expect task results
138 | *
139 | * @return the count of expect task results
140 | */
141 | int getExpectTaskResultCount();
142 |
143 | /**
144 | * set the count of expect task results
145 | *
146 | * @param expectTaskResultCount the count of expect task results
147 | */
148 | void setExpectTaskResultCount(int expectTaskResultCount);
149 |
150 | /**
151 | * Set broadcast or multicast
152 | *
153 | * @param broadcast true is broadcast, false is multicast
154 | */
155 | void setBroadcast(boolean broadcast);
156 | }
157 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/udp/UDPSocketServer.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.udp;
2 |
3 | import android.content.Context;
4 | import android.net.wifi.WifiManager;
5 | import android.util.Log;
6 |
7 | import java.io.IOException;
8 | import java.net.DatagramPacket;
9 | import java.net.DatagramSocket;
10 | import java.net.InetSocketAddress;
11 | import java.net.SocketException;
12 | import java.util.Arrays;
13 |
14 | public class UDPSocketServer {
15 | private static final String TAG = "UDPSocketServer";
16 | private DatagramSocket mServerSocket;
17 | private Context mContext;
18 | private WifiManager.MulticastLock mLock;
19 | private volatile boolean mIsClosed;
20 |
21 | /**
22 | * Constructor of UDP Socket Server
23 | *
24 | * @param port the Socket Server port
25 | * @param socketTimeout the socket read timeout
26 | * @param context the context of the Application
27 | */
28 | public UDPSocketServer(int port, int socketTimeout, Context context) {
29 | this.mContext = context;
30 | try {
31 | this.mServerSocket = new DatagramSocket(null);
32 | this.mServerSocket.setReuseAddress(true);
33 | this.mServerSocket.bind(new InetSocketAddress(port));
34 | this.mServerSocket.setSoTimeout(socketTimeout);
35 | } catch (IOException e) {
36 | Log.w(TAG, "IOException");
37 | e.printStackTrace();
38 | }
39 | this.mIsClosed = false;
40 | WifiManager manager = (WifiManager) mContext.getApplicationContext()
41 | .getSystemService(Context.WIFI_SERVICE);
42 | mLock = manager.createMulticastLock("test wifi");
43 | Log.d(TAG, "mServerSocket is created, socket read timeout: "
44 | + socketTimeout + ", port: " + port);
45 | }
46 |
47 | private synchronized void acquireLock() {
48 | if (mLock != null && !mLock.isHeld()) {
49 | mLock.acquire();
50 | }
51 | }
52 |
53 | private synchronized void releaseLock() {
54 | if (mLock != null && mLock.isHeld()) {
55 | try {
56 | mLock.release();
57 | } catch (Throwable th) {
58 | // ignoring this exception, probably wakeLock was already released
59 | }
60 | }
61 | }
62 |
63 | /**
64 | * Set the socket timeout in milliseconds
65 | *
66 | * @param timeout the timeout in milliseconds or 0 for no timeout.
67 | * @return true whether the timeout is set suc
68 | */
69 | public boolean setSoTimeout(int timeout) {
70 | try {
71 | this.mServerSocket.setSoTimeout(timeout);
72 | return true;
73 | } catch (SocketException e) {
74 | e.printStackTrace();
75 | }
76 | return false;
77 | }
78 |
79 | /**
80 | * Receive one byte from the port and convert it into String
81 | *
82 | * @return
83 | */
84 | public byte receiveOneByte() {
85 | Log.d(TAG, "receiveOneByte() entrance");
86 | try {
87 | acquireLock();
88 | DatagramPacket packet = new DatagramPacket(new byte[1], 1);
89 | mServerSocket.receive(packet);
90 | Log.d(TAG, "receive: " + (packet.getData()[0]));
91 | return packet.getData()[0];
92 | } catch (Exception e) {
93 | e.printStackTrace();
94 | }
95 | return -1;
96 | }
97 |
98 | /**
99 | * Receive specific length bytes from the port and convert it into String
100 | * 21,24,-2,52,-102,-93,-60
101 | * 15,18,fe,34,9a,a3,c4
102 | *
103 | * @return
104 | */
105 | public byte[] receiveSpecLenBytes(int len) {
106 | Log.d(TAG, "receiveSpecLenBytes() entrance: len = " + len);
107 | try {
108 | acquireLock();
109 | DatagramPacket packet = new DatagramPacket(new byte[64], 64);
110 | mServerSocket.receive(packet);
111 | byte[] recDatas = Arrays.copyOf(packet.getData(), packet.getLength());
112 | Log.d(TAG, "received len : " + recDatas.length);
113 | for (int i = 0; i < recDatas.length; i++) {
114 | Log.w(TAG, "recDatas[" + i + "]:" + recDatas[i]);
115 | }
116 | Log.w(TAG, "receiveSpecLenBytes: " + new String(recDatas));
117 | if (recDatas.length != len) {
118 | Log.w(TAG,
119 | "received len is different from specific len, return null");
120 | return null;
121 | }
122 | return recDatas;
123 | } catch (Exception e) {
124 | e.printStackTrace();
125 | }
126 | return null;
127 | }
128 |
129 | public void interrupt() {
130 | Log.i(TAG, "USPSocketServer is interrupt");
131 | close();
132 | }
133 |
134 | public synchronized void close() {
135 | if (!this.mIsClosed) {
136 | Log.w(TAG, "mServerSocket is closed");
137 | mServerSocket.close();
138 | releaseLock();
139 | this.mIsClosed = true;
140 | }
141 | }
142 |
143 | @Override
144 | protected void finalize() throws Throwable {
145 | close();
146 | super.finalize();
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/protocol/DatumCode.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.protocol;
2 |
3 | import com.espressif.iot.esptouch.security.ITouchEncryptor;
4 | import com.espressif.iot.esptouch.task.ICodeData;
5 | import com.espressif.iot.esptouch.util.ByteUtil;
6 | import com.espressif.iot.esptouch.util.CRC8;
7 |
8 | import java.net.InetAddress;
9 | import java.util.LinkedList;
10 |
11 | public class DatumCode implements ICodeData {
12 |
13 | // define by the Esptouch protocol, all of the datum code should add 1 at last to prevent 0
14 | private static final int EXTRA_LEN = 40;
15 | private static final int EXTRA_HEAD_LEN = 5;
16 |
17 | private final LinkedList mDataCodes;
18 |
19 | /**
20 | * Constructor of DatumCode
21 | *
22 | * @param apSsid the Ap's ssid
23 | * @param apBssid the Ap's bssid
24 | * @param apPassword the Ap's password
25 | * @param ipAddress the ip address of the phone or pad
26 | * @param encryptor null use origin data, not null use encrypted data
27 | */
28 | public DatumCode(byte[] apSsid, byte[] apBssid, byte[] apPassword,
29 | InetAddress ipAddress, ITouchEncryptor encryptor) {
30 | // Data = total len(1 byte) + apPwd len(1 byte) + SSID CRC(1 byte) +
31 | // BSSID CRC(1 byte) + TOTAL XOR(1 byte)+ ipAddress(4 byte) + apPwd + apSsid apPwdLen <=
32 | // 105 at the moment
33 |
34 | // total xor
35 | char totalXor = 0;
36 |
37 | char apPwdLen = (char) apPassword.length;
38 | CRC8 crc = new CRC8();
39 | crc.update(apSsid);
40 | char apSsidCrc = (char) crc.getValue();
41 |
42 | crc.reset();
43 | crc.update(apBssid);
44 | char apBssidCrc = (char) crc.getValue();
45 |
46 | char apSsidLen = (char) apSsid.length;
47 |
48 | byte[] ipBytes = ipAddress.getAddress();
49 | int ipLen = ipBytes.length;
50 |
51 | char totalLen = (char) (EXTRA_HEAD_LEN + ipLen + apPwdLen + apSsidLen);
52 |
53 | // build data codes
54 | mDataCodes = new LinkedList<>();
55 | mDataCodes.add(new DataCode(totalLen, 0));
56 | totalXor ^= totalLen;
57 | mDataCodes.add(new DataCode(apPwdLen, 1));
58 | totalXor ^= apPwdLen;
59 | mDataCodes.add(new DataCode(apSsidCrc, 2));
60 | totalXor ^= apSsidCrc;
61 | mDataCodes.add(new DataCode(apBssidCrc, 3));
62 | totalXor ^= apBssidCrc;
63 | // ESPDataCode 4 is null
64 | for (int i = 0; i < ipLen; ++i) {
65 | char c = ByteUtil.convertByte2Uint8(ipBytes[i]);
66 | totalXor ^= c;
67 | mDataCodes.add(new DataCode(c, i + EXTRA_HEAD_LEN));
68 | }
69 |
70 | for (int i = 0; i < apPassword.length; i++) {
71 | char c = ByteUtil.convertByte2Uint8(apPassword[i]);
72 | totalXor ^= c;
73 | mDataCodes.add(new DataCode(c, i + EXTRA_HEAD_LEN + ipLen));
74 | }
75 |
76 | // totalXor will xor apSsidChars no matter whether the ssid is hidden
77 | for (int i = 0; i < apSsid.length; i++) {
78 | char c = ByteUtil.convertByte2Uint8(apSsid[i]);
79 | totalXor ^= c;
80 | mDataCodes.add(new DataCode(c, i + EXTRA_HEAD_LEN + ipLen + apPwdLen));
81 | }
82 |
83 | // add total xor last
84 | mDataCodes.add(4, new DataCode(totalXor, 4));
85 |
86 | // add bssid
87 | int bssidInsertIndex = EXTRA_HEAD_LEN;
88 | for (int i = 0; i < apBssid.length; i++) {
89 | int index = totalLen + i;
90 | char c = ByteUtil.convertByte2Uint8(apBssid[i]);
91 | DataCode dc = new DataCode(c, index);
92 | if (bssidInsertIndex >= mDataCodes.size()) {
93 | mDataCodes.add(dc);
94 | } else {
95 | mDataCodes.add(bssidInsertIndex, dc);
96 | }
97 | bssidInsertIndex += 4;
98 | }
99 | }
100 |
101 | @Override
102 | public byte[] getBytes() {
103 | byte[] datumCode = new byte[mDataCodes.size() * DataCode.DATA_CODE_LEN];
104 | int index = 0;
105 | for (DataCode dc : mDataCodes) {
106 | for (byte b : dc.getBytes()) {
107 | datumCode[index++] = b;
108 | }
109 | }
110 | return datumCode;
111 | }
112 |
113 | @Override
114 | public String toString() {
115 | StringBuilder sb = new StringBuilder();
116 | byte[] dataBytes = getBytes();
117 | for (byte dataByte : dataBytes) {
118 | String hexString = ByteUtil.convertByte2HexString(dataByte);
119 | sb.append("0x");
120 | if (hexString.length() == 1) {
121 | sb.append("0");
122 | }
123 | sb.append(hexString).append(" ");
124 | }
125 | return sb.toString();
126 | }
127 |
128 | @Override
129 | public char[] getU8s() {
130 | byte[] dataBytes = getBytes();
131 | int len = dataBytes.length / 2;
132 | char[] dataU8s = new char[len];
133 | byte high, low;
134 | for (int i = 0; i < len; i++) {
135 | high = dataBytes[i * 2];
136 | low = dataBytes[i * 2 + 1];
137 | dataU8s[i] = (char) (ByteUtil.combine2bytesToU16(high, low) + EXTRA_LEN);
138 | }
139 | return dataU8s;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/task/EsptouchTaskParameter.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.task;
2 |
3 | public class EsptouchTaskParameter implements IEsptouchTaskParameter {
4 |
5 | private static int _datagramCount = 0;
6 | private long mIntervalGuideCodeMillisecond;
7 | private long mIntervalDataCodeMillisecond;
8 | private long mTimeoutGuideCodeMillisecond;
9 | private long mTimeoutDataCodeMillisecond;
10 | private int mTotalRepeatTime;
11 | private int mEsptouchResultOneLen;
12 | private int mEsptouchResultMacLen;
13 | private int mEsptouchResultIpLen;
14 | private int mEsptouchResultTotalLen;
15 | private int mPortListening;
16 | private int mTargetPort;
17 | private int mWaitUdpReceivingMilliseond;
18 | private int mWaitUdpSendingMillisecond;
19 | private int mThresholdSucBroadcastCount;
20 | private int mExpectTaskResultCount;
21 | private boolean mBroadcast = true;
22 |
23 | public EsptouchTaskParameter() {
24 | mIntervalGuideCodeMillisecond = 8;
25 | mIntervalDataCodeMillisecond = 8;
26 | mTimeoutGuideCodeMillisecond = 2000;
27 | mTimeoutDataCodeMillisecond = 4000;
28 | mTotalRepeatTime = 1;
29 | mEsptouchResultOneLen = 1;
30 | mEsptouchResultMacLen = 6;
31 | mEsptouchResultIpLen = 4;
32 | mEsptouchResultTotalLen = 1 + 6 + 4;
33 | mPortListening = 18266;
34 | mTargetPort = 7001;
35 | mWaitUdpReceivingMilliseond = 15000;
36 | mWaitUdpSendingMillisecond = 45000;
37 | mThresholdSucBroadcastCount = 1;
38 | mExpectTaskResultCount = 1;
39 | }
40 |
41 | // the range of the result should be 1-100
42 | private static int __getNextDatagramCount() {
43 | return 1 + (_datagramCount++) % 100;
44 | }
45 |
46 | @Override
47 | public long getIntervalGuideCodeMillisecond() {
48 | return mIntervalGuideCodeMillisecond;
49 | }
50 |
51 | @Override
52 | public long getIntervalDataCodeMillisecond() {
53 | return mIntervalDataCodeMillisecond;
54 | }
55 |
56 | @Override
57 | public long getTimeoutGuideCodeMillisecond() {
58 | return mTimeoutGuideCodeMillisecond;
59 | }
60 |
61 | @Override
62 | public long getTimeoutDataCodeMillisecond() {
63 | return mTimeoutDataCodeMillisecond;
64 | }
65 |
66 | @Override
67 | public long getTimeoutTotalCodeMillisecond() {
68 | return mTimeoutGuideCodeMillisecond + mTimeoutDataCodeMillisecond;
69 | }
70 |
71 | @Override
72 | public int getTotalRepeatTime() {
73 | return mTotalRepeatTime;
74 | }
75 |
76 | @Override
77 | public int getEsptouchResultOneLen() {
78 | return mEsptouchResultOneLen;
79 | }
80 |
81 | @Override
82 | public int getEsptouchResultMacLen() {
83 | return mEsptouchResultMacLen;
84 | }
85 |
86 | @Override
87 | public int getEsptouchResultIpLen() {
88 | return mEsptouchResultIpLen;
89 | }
90 |
91 | @Override
92 | public int getEsptouchResultTotalLen() {
93 | return mEsptouchResultTotalLen;
94 | }
95 |
96 | @Override
97 | public int getPortListening() {
98 | return mPortListening;
99 | }
100 |
101 | // target hostname is : 234.1.1.1, 234.2.2.2, 234.3.3.3 to 234.100.100.100
102 | @Override
103 | public String getTargetHostname() {
104 | if (mBroadcast) {
105 | return "255.255.255.255";
106 | } else {
107 | int count = __getNextDatagramCount();
108 | return "234." + count + "." + count + "." + count;
109 | }
110 | }
111 |
112 | @Override
113 | public int getTargetPort() {
114 | return mTargetPort;
115 | }
116 |
117 | @Override
118 | public int getWaitUdpReceivingMillisecond() {
119 | return mWaitUdpReceivingMilliseond;
120 | }
121 |
122 | @Override
123 | public int getWaitUdpSendingMillisecond() {
124 | return mWaitUdpSendingMillisecond;
125 | }
126 |
127 | @Override
128 | public int getWaitUdpTotalMillisecond() {
129 | return mWaitUdpReceivingMilliseond + mWaitUdpSendingMillisecond;
130 | }
131 |
132 | @Override
133 | public void setWaitUdpTotalMillisecond(int waitUdpTotalMillisecond) {
134 | if (waitUdpTotalMillisecond < mWaitUdpReceivingMilliseond
135 | + getTimeoutTotalCodeMillisecond()) {
136 | // if it happen, even one turn about sending udp broadcast can't be
137 | // completed
138 | throw new IllegalArgumentException(
139 | "waitUdpTotalMillisecod is invalid, "
140 | + "it is less than mWaitUdpReceivingMilliseond + getTimeoutTotalCodeMillisecond()");
141 | }
142 | mWaitUdpSendingMillisecond = waitUdpTotalMillisecond
143 | - mWaitUdpReceivingMilliseond;
144 | }
145 |
146 | @Override
147 | public int getThresholdSucBroadcastCount() {
148 | return mThresholdSucBroadcastCount;
149 | }
150 |
151 | @Override
152 | public int getExpectTaskResultCount() {
153 | return this.mExpectTaskResultCount;
154 | }
155 |
156 | @Override
157 | public void setExpectTaskResultCount(int expectTaskResultCount) {
158 | this.mExpectTaskResultCount = expectTaskResultCount;
159 | }
160 |
161 | @Override
162 | public void setBroadcast(boolean broadcast) {
163 | mBroadcast = broadcast;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/esptouch-v2/src/main/java/com/espressif/iot/esptouch2/provision/TouchNetUtil.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch2.provision;
2 |
3 | import android.net.DhcpInfo;
4 | import android.net.wifi.WifiInfo;
5 | import android.net.wifi.WifiManager;
6 |
7 | import java.lang.reflect.InvocationTargetException;
8 | import java.lang.reflect.Method;
9 | import java.net.DatagramSocket;
10 | import java.net.Inet4Address;
11 | import java.net.Inet6Address;
12 | import java.net.InetAddress;
13 | import java.net.NetworkInterface;
14 | import java.net.SocketException;
15 | import java.net.UnknownHostException;
16 | import java.util.Enumeration;
17 |
18 | public class TouchNetUtil {
19 | public static boolean isWifiConnected(WifiManager wifiManager) {
20 | WifiInfo wifiInfo = wifiManager.getConnectionInfo();
21 | return wifiInfo != null
22 | && wifiInfo.getNetworkId() != -1
23 | && !"".equals(wifiInfo.getSSID());
24 | }
25 |
26 | public static byte[] getRawSsidBytes(WifiInfo info) {
27 | try {
28 | Method method = info.getClass().getMethod("getWifiSsid");
29 | method.setAccessible(true);
30 | Object wifiSsid = method.invoke(info);
31 | if (wifiSsid == null) {
32 | return null;
33 | }
34 | method = wifiSsid.getClass().getMethod("getOctets");
35 | method.setAccessible(true);
36 | return (byte[]) method.invoke(wifiSsid);
37 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NullPointerException e) {
38 | e.printStackTrace();
39 | }
40 | return null;
41 | }
42 |
43 | public static byte[] getRawSsidBytesOrElse(WifiInfo info, byte[] orElse) {
44 | byte[] raw = getRawSsidBytes(info);
45 | return raw != null ? raw : orElse;
46 | }
47 |
48 | public static String getSsidString(WifiInfo info) {
49 | String ssid = info.getSSID();
50 | if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
51 | ssid = ssid.substring(1, ssid.length() - 1);
52 | }
53 | return ssid;
54 | }
55 |
56 | public static InetAddress getBroadcastAddress(WifiManager wifi) {
57 | DhcpInfo dhcp = wifi.getDhcpInfo();
58 | if (dhcp != null) {
59 | int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
60 | byte[] quads = new byte[4];
61 | for (int k = 0; k < 4; k++) {
62 | quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
63 | }
64 | try {
65 | return InetAddress.getByAddress(quads);
66 | } catch (UnknownHostException e) {
67 | e.printStackTrace();
68 | }
69 | }
70 |
71 | try {
72 | return InetAddress.getByName("255.255.255.255");
73 | } catch (UnknownHostException e) {
74 | e.printStackTrace();
75 | }
76 | // Impossible arrive here
77 | return null;
78 | }
79 |
80 | public static boolean is5G(int frequency) {
81 | return frequency > 4900 && frequency < 5900;
82 | }
83 |
84 | public static InetAddress getAddress(int ipAddress) {
85 | byte[] ip = new byte[]{
86 | (byte) (ipAddress & 0xff),
87 | (byte) ((ipAddress >> 8) & 0xff),
88 | (byte) ((ipAddress >> 16) & 0xff),
89 | (byte) ((ipAddress >> 24) & 0xff)
90 | };
91 |
92 | try {
93 | return InetAddress.getByAddress(ip);
94 | } catch (UnknownHostException e) {
95 | e.printStackTrace();
96 | // Impossible arrive here
97 | return null;
98 | }
99 | }
100 |
101 | private static InetAddress getAddress(boolean isIPv4) {
102 | try {
103 | Enumeration enums = NetworkInterface.getNetworkInterfaces();
104 | while (enums.hasMoreElements()) {
105 | NetworkInterface ni = enums.nextElement();
106 | Enumeration addrs = ni.getInetAddresses();
107 | while (addrs.hasMoreElements()) {
108 | InetAddress address = addrs.nextElement();
109 | if (!address.isLoopbackAddress()) {
110 | if (isIPv4 && address instanceof Inet4Address) {
111 | return address;
112 | }
113 | if (!isIPv4 && address instanceof Inet6Address) {
114 | return address;
115 | }
116 | }
117 | }
118 | }
119 | } catch (SocketException e) {
120 | e.printStackTrace();
121 | }
122 | return null;
123 | }
124 |
125 | public static InetAddress getIPv4Address() {
126 | return getAddress(true);
127 | }
128 |
129 | public static InetAddress getIPv6Address() {
130 | return getAddress(false);
131 | }
132 |
133 | /**
134 | * @param bssid the bssid like aa:bb:cc:dd:ee:ff
135 | * @return byte array converted from bssid
136 | */
137 | public static byte[] convertBssid2Bytes(String bssid) {
138 | String[] bssidSplits = bssid.split(":");
139 | if (bssidSplits.length != 6) {
140 | throw new IllegalArgumentException("Invalid bssid format");
141 | }
142 | byte[] result = new byte[bssidSplits.length];
143 | for (int i = 0; i < bssidSplits.length; i++) {
144 | result[i] = (byte) Integer.parseInt(bssidSplits[i], 16);
145 | }
146 | return result;
147 | }
148 |
149 | public static DatagramSocket createUdpSocket() {
150 | for (int port = 23233; port < 0xffff; ++port) {
151 | try {
152 | return new DatagramSocket(port);
153 | } catch (SocketException ignored) {
154 | }
155 | }
156 |
157 | return null;
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/esptouch-v2/src/main/java/com/espressif/iot/esptouch2/provision/EspProvisioningRequest.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch2.provision;
2 |
3 | import android.content.Context;
4 | import android.net.wifi.WifiInfo;
5 | import android.net.wifi.WifiManager;
6 | import android.os.Parcel;
7 | import android.os.Parcelable;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 |
12 | import java.net.InetAddress;
13 | import java.net.UnknownHostException;
14 |
15 | public class EspProvisioningRequest implements Parcelable {
16 | private static final int SSID_LENGTH_MAX = 32;
17 | private static final int PASSWORD_LENGTH_MAX = 64;
18 | private static final int BSSID_LENGTH = 6;
19 | private static final int AES_KEY_LENGTH = 16;
20 |
21 | public static final int RESERVED_LENGTH_MAX = 64;
22 |
23 | public static final int SECURITY_V1 = 1;
24 | public static final int SECURITY_V2 = 2;
25 |
26 | public final InetAddress address;
27 |
28 | public final byte[] ssid;
29 | public final byte[] bssid;
30 | public final byte[] password;
31 |
32 | public final byte[] reservedData;
33 |
34 | public final byte[] aesKey;
35 | public final int securityVer;
36 |
37 | private EspProvisioningRequest(InetAddress address, byte[] ssid, byte[] bssid, byte[] password,
38 | byte[] reservedData, byte[] aesKey, int securityVer) {
39 | this.address = address;
40 | this.ssid = ssid;
41 | this.bssid = bssid;
42 | this.password = password;
43 | this.reservedData = reservedData;
44 | this.aesKey = aesKey;
45 | this.securityVer = securityVer;
46 | }
47 |
48 | private EspProvisioningRequest(Parcel in) {
49 | address = (InetAddress) in.readSerializable();
50 | ssid = in.createByteArray();
51 | bssid = in.createByteArray();
52 | password = in.createByteArray();
53 | reservedData = in.createByteArray();
54 | aesKey = in.createByteArray();
55 | securityVer = in.readInt();
56 | }
57 |
58 | public static final Creator CREATOR = new Creator() {
59 | @Override
60 | public EspProvisioningRequest createFromParcel(Parcel in) {
61 | return new EspProvisioningRequest(in);
62 | }
63 |
64 | @Override
65 | public EspProvisioningRequest[] newArray(int size) {
66 | return new EspProvisioningRequest[size];
67 | }
68 | };
69 |
70 | @Override
71 | public int describeContents() {
72 | return 0;
73 | }
74 |
75 | @Override
76 | public void writeToParcel(Parcel dest, int flags) {
77 | dest.writeSerializable(address);
78 | dest.writeByteArray(ssid);
79 | dest.writeByteArray(bssid);
80 | dest.writeByteArray(password);
81 | dest.writeByteArray(reservedData);
82 | dest.writeByteArray(aesKey);
83 | dest.writeInt(securityVer);
84 | }
85 |
86 | public static class Builder {
87 | private InetAddress address;
88 |
89 | private byte[] ssid = null;
90 | private byte[] bssid = null;
91 | private byte[] password = null;
92 |
93 | private byte[] reservedData;
94 |
95 | private byte[] aesKey;
96 | private int secretVer = SECURITY_V1;
97 |
98 | private Context mContext;
99 |
100 | public Builder(Context context) {
101 | mContext = context.getApplicationContext();
102 | }
103 |
104 | public Builder setSSID(@Nullable byte[] ssid) {
105 | if (ssid != null && ssid.length > SSID_LENGTH_MAX) {
106 | throw new IllegalArgumentException("SSID length is greater than 32");
107 | }
108 | this.ssid = ssid;
109 | return this;
110 | }
111 |
112 | public Builder setBSSID(@NonNull byte[] bssid) {
113 | if (bssid.length != BSSID_LENGTH) {
114 | throw new IllegalArgumentException("Invalid BSSID data");
115 | }
116 | this.bssid = bssid;
117 | return this;
118 | }
119 |
120 | public Builder setPassword(@Nullable byte[] password) {
121 | if (password != null && password.length > PASSWORD_LENGTH_MAX) {
122 | throw new IllegalArgumentException("Password length is greater than 64");
123 | }
124 |
125 | this.password = password;
126 | return this;
127 | }
128 |
129 | public Builder setReservedData(@Nullable byte[] data) {
130 | if (data != null && data.length > RESERVED_LENGTH_MAX) {
131 | throw new IllegalArgumentException("ReservedData length is greater than 64");
132 | }
133 |
134 | this.reservedData = data;
135 | return this;
136 | }
137 |
138 | public Builder setAESKey(@Nullable byte[] aesKey) {
139 | if (aesKey != null && aesKey.length != AES_KEY_LENGTH) {
140 | throw new IllegalArgumentException("AES Key must be null or 16 bytes");
141 | }
142 |
143 | this.aesKey = aesKey;
144 | return this;
145 | }
146 |
147 | public Builder setSecurityVer(int securityVer) {
148 | switch (securityVer) {
149 | case SECURITY_V1:
150 | case SECURITY_V2:
151 | break;
152 | default:
153 | throw new IllegalArgumentException(("Security ver is illegal"));
154 | }
155 | this.secretVer = securityVer;
156 | return this;
157 | }
158 |
159 | public EspProvisioningRequest build() {
160 | WifiManager wm = (WifiManager) mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
161 | assert wm != null;
162 | WifiInfo wifiInfo = wm.getConnectionInfo();
163 | if (ssid == null || ssid.length == 0) {
164 | if (wifiInfo.getHiddenSSID()) {
165 | ssid = TouchNetUtil.getRawSsidBytes(wifiInfo);
166 | if (ssid == null) {
167 | ssid = TouchNetUtil.getSsidString(wifiInfo).getBytes();
168 | }
169 | }
170 | }
171 | if (bssid == null) {
172 | bssid = TouchNetUtil.convertBssid2Bytes(wifiInfo.getBSSID());
173 | }
174 | if (address == null) {
175 | if (wifiInfo.getIpAddress() != 0) {
176 | address = TouchNetUtil.getAddress(wifiInfo.getIpAddress());
177 | }
178 | }
179 | if (address == null) {
180 | address = TouchNetUtil.getIPv4Address();
181 | }
182 | if (address == null) {
183 | address = TouchNetUtil.getIPv6Address();
184 | }
185 | if (address == null) {
186 | try {
187 | address = InetAddress.getByName("255.255.255.255");
188 | } catch (UnknownHostException e) {
189 | e.printStackTrace();
190 | }
191 | }
192 |
193 | return new EspProvisioningRequest(address, ssid, bssid, password, reservedData, aesKey, secretVer);
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/docs/esptouch-user-guide-en.md:
--------------------------------------------------------------------------------
1 | [[中文]](esptouch-user-guide-cn.md)
2 |
3 | # ESP-TOUCH User Guide
4 |
5 | ---
6 |
7 | ## Technology Overview
8 |
9 | ESP-TOUCH protocol implements the Smart Config technology to enable seamless Wi-Fi provisioning for ESP8266 and ESP32 embedded devices (hereafter "the device") through smartphone-based configuration.
10 |
11 |
12 |
13 | Since the device is not connected to the network initially, the ESP-TOUCH application cannot send any information to the device directly. With the ESP-TOUCH communication protocol, a device with Wi-Fi access capabilities, such as a smartphone, can send a series of UDP packets to the Wi-Fi Access Point (AP), encoding the SSID and password into the Length field of each of these UDP packets. The device can then reach the UDP packets, obtaining and parsing out the required information.
14 |
15 | The data packet structure is shown below:
16 |
17 | | Field | Length (Bytes) | Description |
18 | |--------|----------------|--------------------------------------|
19 | | DA | 6 | Destination MAC Address |
20 | | SA | 6 | Source MAC Address |
21 | | Length | 2 | Encodes SSID and password |
22 | | LLC | 3 | Logical Link Control |
23 | | SNAP | 5 | Subnetwork Access Protocol |
24 | | DATA | Variable | Payload |
25 | | FCS | 4 | Frame Check Sequence |
26 |
27 |
28 | ## ESP-TOUCH Operations
29 |
30 | ### Functional Overview
31 |
32 | - **Supported Platforms**:
33 | - ESP8266: OS SDK, NonOS SDK
34 | - ESP32: ESP-IDF Framework
35 | - **Protocol Compatibility**: Integrated with AirKiss protocol developed by WeChat.
36 | - Devices can be configured using either the ESP-TOUCH app or the WeChat client.
37 |
38 | ### Operation Process
39 |
40 | 1. Prepare a device that supports ESP-TOUCH, and enable its Smart Config function.
41 | 2. Connect the smartphone to the target router.
42 | 3. Open the ESP-TOUCH App on the smartphone.
43 | 4. Input the router’s SSID and password to connect the device to the router. If the router is open (unencrypted), leave the password field blank.
44 |
45 | **Important Notes**
46 |
47 | - **Distance Impact**: If the device is far from the router, it may take longer to connect.
48 | - **Router Status**: Ensure the router is powered on before configuration.
49 | - **Timeout Mechanism**:
50 | - The App returns a configuration failure message if connection isn't established within the timeout period.
51 | - The device automatically restarts Smart Config if credentials aren't obtained before timeout.
52 | - Adjust timeout duration using `esptouch_set_timeout(uint8 time_s)` or `esp_smartconfig_set_timeout(uint8 time_s)`.
53 | - **Operational Modes**:
54 | - **ESP8266**: Sniffer mode must be enabled, with Station/soft-AP modes disabled.
55 | - **ESP32**: Sniffer and Station modes can be enabled at the same time.
56 | - **IP Exchange**: After configuration, the smartphone obtains the device's IP address, while the device returns the smartphone's IP, enabling custom LAN communication.
57 | - **AP Isolation**: If AP isolation is enabled on the router, the App might not receive the success notification.
58 | - **Multi-Device Support**: The App can configure multiple devices to connect to the same router simultaneously.
59 | - **Network Limitations**: 5 GHz frequency band and 802.11ac protocol are not supported.
60 |
61 | ## Performance Analysis
62 |
63 | The ESP-TOUCH communication model can be understood as a unidirectional channel with a certain packet error rate. However, this error rate varies depending on the bandwidth. Typically:
64 |
65 | - For 20 MHz bandwidth, the packet error rate is approximately 0–5%
66 | - For 40 MHz bandwidth, it ranges is 0–17%
67 |
68 | Assuming that the maximum length of data to be transmitted is 104 bytes, if no error-correcting algorithm is used, it is difficult to ensure that the data can be transmitted over limited rounds of data transfer.
69 |
70 | ### Cumulative Error-Correcting Algorithm
71 |
72 | To address this, ESP-TOUCH implements a cumulative error-correcting algorithm to complete data transmission within a limited number of rounds. The theoretical basis of this algorithm is that the probability of an error occurring on the same bit of data during multiple rounds of data transmission is very low. Therefore, it is possible to accumulate results of multiple rounds of data transfer, where there is great potential for one bit of erroneous data in one round to lead to the correct corresponding value in other rounds, thus ensuring the completion of data transfer within a limited amount of time.
73 |
74 | The success rate of data transmission can be generalized as:
75 |
76 | Success Rate = [1 – (1 – P)k ]l
77 |
78 | Where:
79 | - P: Single-packet success rate
80 | - k: Transmission rounds
81 | - l: Data length (in bytes)
82 |
83 | **Typical Scenario Analysis**:
84 | - **20 MHz Bandwidth** (*P = 0.95*): 104-byte data achieves a 95% success rate
85 | - **40 MHz Bandwidth** (*P = 0.83*): 72-byte data achieves an 83% success rate
86 |
87 | The tables below show the probability of the data transmission success rate and the transmission time when the cumulative error-correcting algorithm is adopted.
88 |
89 | **Table: 20 MHz Bandwidth Analysis**
90 |
91 | | Rounds | Time (s) – 104 Bytes | Success Rate – 104 Bytes | Time (s) – 72 Bytes | Success Rate – 72 Bytes |
92 | |--------|----------------------|---------------------------|---------------------|--------------------------|
93 | | 1 | 4.68 | 0.0048 | 3.24 | 0.0249 |
94 | | 2 | 9.36 | 0.771 | 6.48 | 0.835 |
95 | | 3 | 14.04 | 0.987 | 9.72 | 0.991 |
96 | | 4 | 18.72 | 0.9994 | 12.90 | 0.9996 |
97 | | 5 | 23.40 | 0.99997 | 16.20 | 0.99998 |
98 | | 6 | 28.08 | 0.999998 | 19.40 | 0.99999 |
99 |
100 | **Table: 40 MHz Bandwidth Analysis**
101 |
102 | | Rounds | Time (s) – 104 Bytes | Success Rate – 104 Bytes | Time (s) – 72 Bytes | Success Rate – 72 Bytes |
103 | |--------|----------------------|---------------------------|---------------------|--------------------------|
104 | | 1 | 4.68 | 3.84e-9 | 3.24 | 1.49e-6 |
105 | | 2 | 9.36 | 0.0474 | 6.48 | 0.121 |
106 | | 3 | 14.04 | 0.599 | 9.72 | 0.701 |
107 | | 4 | 18.72 | 0.917 | 12.90 | 0.942 |
108 | | 5 | 23.40 | 0.985 | 16.20 | 0.989 |
109 | | 6 | 28.08 | 0.997 | 19.40 | 0.998 |
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/espressif/esptouch/android/EspTouchActivityAbs.java:
--------------------------------------------------------------------------------
1 | package com.espressif.esptouch.android;
2 |
3 | import android.Manifest;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 | import android.location.LocationManager;
7 | import android.net.wifi.WifiInfo;
8 | import android.net.wifi.WifiManager;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.text.Spannable;
12 | import android.text.SpannableString;
13 | import android.text.SpannableStringBuilder;
14 | import android.text.style.ForegroundColorSpan;
15 | import android.view.Menu;
16 | import android.view.MenuItem;
17 |
18 | import androidx.annotation.Nullable;
19 | import androidx.appcompat.app.AlertDialog;
20 | import androidx.appcompat.app.AppCompatActivity;
21 | import androidx.core.location.LocationManagerCompat;
22 |
23 | import com.espressif.iot.esptouch2.provision.TouchNetUtil;
24 |
25 | import java.net.InetAddress;
26 |
27 | public abstract class EspTouchActivityAbs extends AppCompatActivity {
28 | private static final int MENU_ITEM_ABOUT = 0;
29 |
30 | private WifiManager mWifiManager;
31 |
32 | protected abstract String getEspTouchVersion();
33 |
34 | protected static class StateResult {
35 | public CharSequence message = null;
36 |
37 | public boolean enable = true;
38 |
39 | public boolean permissionGranted = false;
40 |
41 | public boolean wifiConnected = false;
42 |
43 | public boolean is5G = false;
44 | public InetAddress address = null;
45 | public String ssid = null;
46 | public byte[] ssidBytes = null;
47 | public String bssid = null;
48 | }
49 |
50 | @Override
51 | protected void onCreate(@Nullable Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 |
54 | mWifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
55 | }
56 |
57 | private void showAboutDialog() {
58 | String esptouchVerText = getEspTouchVersion();
59 | String appVer = "";
60 | PackageManager packageManager = getPackageManager();
61 | try {
62 | PackageInfo info = packageManager.getPackageInfo(getPackageName(), 0);
63 | appVer = info.versionName;
64 | } catch (PackageManager.NameNotFoundException e) {
65 | e.printStackTrace();
66 | }
67 |
68 | CharSequence[] items = new CharSequence[]{
69 | getString(R.string.about_app_version, appVer),
70 | esptouchVerText
71 | };
72 | new AlertDialog.Builder(this)
73 | .setTitle(R.string.menu_item_about)
74 | .setIcon(R.drawable.baseline_info_black_24)
75 | .setItems(items, null)
76 | .show();
77 | }
78 |
79 | @Override
80 | public boolean onCreateOptionsMenu(Menu menu) {
81 | menu.add(Menu.NONE, MENU_ITEM_ABOUT, 0, R.string.menu_item_about)
82 | .setIcon(R.drawable.ic_info_outline_white_24dp)
83 | .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
84 | return super.onCreateOptionsMenu(menu);
85 | }
86 |
87 | @Override
88 | public boolean onOptionsItemSelected(MenuItem item) {
89 | switch (item.getItemId()) {
90 | case android.R.id.home:
91 | onBackPressed();
92 | return true;
93 | case MENU_ITEM_ABOUT:
94 | showAboutDialog();
95 | return true;
96 | }
97 |
98 | return super.onOptionsItemSelected(item);
99 | }
100 |
101 | protected StateResult checkState() {
102 | StateResult result = new StateResult();
103 | checkPermission(result);
104 | if (!result.enable) {
105 | return result;
106 | }
107 |
108 | checkLocation(result);
109 | if (!result.enable) {
110 | return result;
111 | }
112 |
113 | checkWifi(result);
114 |
115 | return result;
116 | }
117 |
118 | private StateResult checkPermission(StateResult result) {
119 | result.permissionGranted = true;
120 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
121 | boolean locationGranted = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
122 | == PackageManager.PERMISSION_GRANTED;
123 | if (!locationGranted) {
124 | String[] splits = getString(R.string.esptouch_message_permission).split("\n");
125 | if (splits.length != 2) {
126 | throw new IllegalArgumentException("Invalid String @RES esptouch_message_permission");
127 | }
128 | SpannableStringBuilder ssb = new SpannableStringBuilder(splits[0]);
129 | ssb.append('\n');
130 | SpannableString clickMsg = new SpannableString(splits[1]);
131 | ForegroundColorSpan clickSpan = new ForegroundColorSpan(0xFF0022FF);
132 | clickMsg.setSpan(clickSpan, 0, clickMsg.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
133 | ssb.append(clickMsg);
134 | result.message = ssb;
135 |
136 |
137 | result.permissionGranted = false;
138 | result.enable = false;
139 | }
140 | }
141 |
142 | return result;
143 | }
144 |
145 | private StateResult checkLocation(StateResult result) {
146 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
147 | LocationManager manager = getSystemService(LocationManager.class);
148 | boolean enable = manager != null && LocationManagerCompat.isLocationEnabled(manager);
149 | if (!enable) {
150 | result.message = getString(R.string.esptouch_message_location);
151 | result.enable = false;
152 | return result;
153 | }
154 | }
155 |
156 | return result;
157 | }
158 |
159 | private StateResult checkWifi(StateResult result) {
160 | result.wifiConnected = false;
161 | WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
162 | boolean connected = TouchNetUtil.isWifiConnected(mWifiManager);
163 | if (!connected) {
164 | result.message = getString(R.string.esptouch_message_wifi_connection);
165 | return result;
166 | }
167 |
168 | String ssid = TouchNetUtil.getSsidString(wifiInfo);
169 | int ipValue = wifiInfo.getIpAddress();
170 | if (ipValue != 0) {
171 | result.address = TouchNetUtil.getAddress(wifiInfo.getIpAddress());
172 | } else {
173 | result.address = TouchNetUtil.getIPv4Address();
174 | if (result.address == null) {
175 | result.address = TouchNetUtil.getIPv6Address();
176 | }
177 | }
178 |
179 | result.wifiConnected = true;
180 | result.message = "";
181 | result.is5G = TouchNetUtil.is5G(wifiInfo.getFrequency());
182 | if (result.is5G) {
183 | result.message = getString(R.string.esptouch_message_wifi_frequency);
184 | }
185 | result.ssid = ssid;
186 | result.ssidBytes = TouchNetUtil.getRawSsidBytesOrElse(wifiInfo, ssid.getBytes());
187 | result.bssid = wifiInfo.getBSSID();
188 |
189 | result.enable = result.wifiConnected;
190 |
191 | return result;
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_esptouch.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
24 |
25 |
35 |
36 |
47 |
48 |
58 |
59 |
68 |
69 |
75 |
76 |
77 |
85 |
86 |
92 |
93 |
94 |
101 |
102 |
108 |
109 |
115 |
116 |
117 |
125 |
126 |
132 |
133 |
134 |
142 |
143 |
147 |
148 |
157 |
158 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/espressif/esptouch/android/v2/EspProvisioningActivity.java:
--------------------------------------------------------------------------------
1 | package com.espressif.esptouch.android.v2;
2 |
3 | import android.graphics.Color;
4 | import android.net.wifi.WifiManager;
5 | import android.os.Bundle;
6 | import android.util.Log;
7 | import android.view.LayoutInflater;
8 | import android.view.MenuItem;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.TextView;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 | import androidx.appcompat.app.AppCompatActivity;
16 | import androidx.lifecycle.Observer;
17 | import androidx.recyclerview.widget.DividerItemDecoration;
18 | import androidx.recyclerview.widget.OrientationHelper;
19 | import androidx.recyclerview.widget.RecyclerView;
20 |
21 | import com.espressif.esptouch.android.EspTouchApp;
22 | import com.espressif.esptouch.android.R;
23 | import com.espressif.esptouch.android.databinding.ActivityProvisionBinding;
24 | import com.espressif.iot.esptouch2.provision.EspProvisioner;
25 | import com.espressif.iot.esptouch2.provision.EspProvisioningListener;
26 | import com.espressif.iot.esptouch2.provision.EspProvisioningRequest;
27 | import com.espressif.iot.esptouch2.provision.EspProvisioningResult;
28 | import com.espressif.iot.esptouch2.provision.TouchNetUtil;
29 |
30 | import java.util.ArrayList;
31 | import java.util.List;
32 |
33 | public class EspProvisioningActivity extends AppCompatActivity {
34 | private static final String TAG = EspProvisioningActivity.class.getSimpleName();
35 |
36 | public static final String KEY_PROVISION = "provision";
37 | public static final String KEY_PROVISION_REQUEST = "provision_request";
38 | public static final String KEY_DEVICE_COUNT = "device_count";
39 |
40 | private List mStations;
41 | private StationAdapter mStationAdapter;
42 |
43 | private EspProvisioner mProvisioner;
44 |
45 | private WifiManager mWifiManager;
46 |
47 | private Observer mBroadcastObserver;
48 |
49 | private ActivityProvisionBinding mBinding;
50 |
51 | private boolean mWifiFailed = false;
52 |
53 | private long mTime;
54 |
55 | private int mWillProvisioningCount = -1;
56 |
57 | @Override
58 | protected void onCreate(@Nullable Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | mBinding = ActivityProvisionBinding.inflate(getLayoutInflater());
61 | setContentView(mBinding.getRoot());
62 |
63 | if (getSupportActionBar() != null) {
64 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
65 | }
66 |
67 | EspProvisioningRequest request = getIntent().getParcelableExtra(KEY_PROVISION_REQUEST);
68 | mWillProvisioningCount = getIntent().getIntExtra(KEY_DEVICE_COUNT, -1);
69 | assert request != null;
70 | mProvisioner = new EspProvisioner(getApplicationContext());
71 |
72 | RecyclerView recyclerView = findViewById(R.id.recyclerView);
73 | recyclerView.addItemDecoration(new DividerItemDecoration(this, OrientationHelper.VERTICAL));
74 | mStations = new ArrayList<>();
75 | mStationAdapter = new StationAdapter();
76 | recyclerView.setAdapter(mStationAdapter);
77 |
78 | mBinding.stopBtn.setOnClickListener(v -> {
79 | v.setEnabled(false);
80 | mProvisioner.stopProvisioning();
81 | });
82 |
83 | mWifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
84 | mBroadcastObserver = action -> {
85 | boolean connected = TouchNetUtil.isWifiConnected(mWifiManager);
86 | if (!connected && mProvisioner.isProvisioning()) {
87 | mWifiFailed = true;
88 | mBinding.messageView.setText(getString(R.string.esptouch2_provisioning_wifi_disconnect));
89 | mProvisioner.stopProvisioning();
90 | }
91 | };
92 | EspTouchApp.getInstance().observeBroadcastForever(mBroadcastObserver);
93 |
94 | mTime = System.currentTimeMillis();
95 | mProvisioner.startProvisioning(request, new ProvisionListener());
96 | }
97 |
98 | @Override
99 | protected void onDestroy() {
100 | super.onDestroy();
101 |
102 | EspTouchApp.getInstance().removeBroadcastObserver(mBroadcastObserver);
103 | mProvisioner.stopProvisioning();
104 | }
105 |
106 | @Override
107 | public boolean onOptionsItemSelected(@NonNull MenuItem item) {
108 | if (item.getItemId() == android.R.id.home) {
109 | onBackPressed();
110 | return true;
111 | }
112 |
113 | return super.onOptionsItemSelected(item);
114 | }
115 |
116 | @Override
117 | public void onBackPressed() {
118 | super.onBackPressed();
119 |
120 | mProvisioner.stopProvisioning();
121 | mProvisioner.close();
122 | }
123 |
124 | private static class StationHolder extends RecyclerView.ViewHolder {
125 | TextView text1;
126 | TextView text2;
127 |
128 | StationHolder(@NonNull View itemView) {
129 | super(itemView);
130 |
131 | text1 = itemView.findViewById(android.R.id.text1);
132 | text1.setTextColor(Color.BLACK);
133 | text2 = itemView.findViewById(android.R.id.text2);
134 | text2.setTextColor(Color.BLACK);
135 | }
136 | }
137 |
138 | private class StationAdapter extends RecyclerView.Adapter {
139 |
140 | @NonNull
141 | @Override
142 | public StationHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
143 | View itemView = LayoutInflater.from(getApplicationContext()).inflate(android.R.layout.simple_list_item_2,
144 | parent, false);
145 | return new StationHolder(itemView);
146 | }
147 |
148 | @Override
149 | public void onBindViewHolder(@NonNull StationHolder holder, int position) {
150 | EspProvisioningResult station = mStations.get(position);
151 |
152 | holder.text1.setText(getString(R.string.esptouch2_provisioning_result_bssid, station.bssid));
153 | holder.text2.setText(getString(R.string.esptouch2_provisioning_result_address,
154 | station.address.getHostAddress()));
155 | }
156 |
157 | @Override
158 | public int getItemCount() {
159 | return mStations.size();
160 | }
161 | }
162 |
163 |
164 | private class ProvisionListener implements EspProvisioningListener {
165 | @Override
166 | public void onStart() {
167 | Log.d(TAG, "ProvisionListener onStart: ");
168 | }
169 |
170 | @Override
171 | public void onResponse(EspProvisioningResult result) {
172 | String mac = result.bssid;
173 | String host = result.address.getHostAddress();
174 | Log.d(TAG, "ProvisionListener onResponse: " + mac + " " + host);
175 | runOnUiThread(() -> {
176 | mStations.add(result);
177 | mStationAdapter.notifyItemInserted(mStations.size() - 1);
178 |
179 | if (mWillProvisioningCount > 0 && mStations.size() >= mWillProvisioningCount) {
180 | mProvisioner.stopProvisioning();
181 | }
182 | });
183 | }
184 |
185 | @Override
186 | public void onStop() {
187 | Log.d(TAG, "ProvisionListener onStop: ");
188 | runOnUiThread(() -> {
189 | if (!mWifiFailed && mStations.isEmpty()) {
190 | mBinding.messageView.setText(R.string.esptouch2_provisioning_result_none);
191 | }
192 | mBinding.stopBtn.setEnabled(false);
193 | mBinding.progressView.setVisibility(View.GONE);
194 | });
195 | mTime = System.currentTimeMillis() - mTime;
196 | Log.e(TAG, "Provisioning task cost " + mTime);
197 | }
198 |
199 | @Override
200 | public void onError(Exception e) {
201 | Log.w(TAG, "ProvisionListener onError: ", e);
202 | mProvisioner.stopProvisioning();
203 | runOnUiThread(() -> {
204 | String message = getString(R.string.esptouch2_provisioning_result_exception,
205 | e.getLocalizedMessage());
206 | mBinding.messageView.setText(message);
207 | });
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/log/log-en.md:
--------------------------------------------------------------------------------
1 | [[简体中文]](log-zh-rCN.md)
2 |
3 | # Update Log
4 |
5 | ## v2.4.0
6 | - EspTouchV2
7 | - Add security mode options
8 |
9 | ## v2.3.2
10 | - Allow the app to smart config in 5G frequency for ESP32-C5
11 | - EspTouchV2
12 | - Update esptouch-v2 lib to 2.2.1
13 | - EspTouch
14 | - Update esptouch lib to 1.1.1
15 |
16 | ## v2.3.1
17 | - Update gradle
18 | - Update `targetSdkVersion` to 32
19 | - Change `sourceCompatibility` and `targetCompatibility` to JavaVersion.VERSION_1_8
20 | - EspTouchV2
21 | - Update esptouch-v2 lib to 2.2.0
22 | - EspTouch
23 | - Update esptouch lib to 1.1.0
24 |
25 | ## v2.3.0
26 | - EspTouchV2
27 | - Support set device count for provisioning
28 | - Change custom data max length to 64 bytes
29 | - Update esptouch-v2 lib to 2.1.0
30 |
31 | ## v2.0.0
32 | - Support EspTouchV2
33 | - EspTouchV2 is not compatible with EspTouch
34 |
35 | ## v1.1.1
36 | EspTouch v0.3.7.2
37 | - Optimize permission check
38 |
39 | ## v1.1.0
40 | EspTouch v0.3.7.2
41 | - Migrate to AndroidX
42 | - Change App theme
43 | - Add signature
44 |
45 | ## v1.0.0
46 | EspTouch v0.3.7.1
47 | - Add Chinese
48 | - Check location(GPS) state if no wifi info on Android 9
49 | - Hide EsptouchTask's aes constructor, device doesn't support currently
50 |
51 | ## v0.3.7.0
52 | - Request location permission if Android SDK greater than 27
53 | - Add option to select Broadcast or Multicast
54 |
55 | ## v0.3.6.2
56 | - Add new constructor for EsptouchTask
57 | ```
58 | String ssidStr = "ssid";
59 | byte[] ssid = ByteUtil.getBytesByString(ssid);
60 | String bssidStr = "aa:bb:cc:dd:ee:ff";
61 | byte [] bssid = EspNetUtil.parseBssid2bytes(bssidStr);
62 | String passwordStr = "password";
63 | byte[] password = ByteUtil.getBytesByString(passwordStr);
64 | EspAes aes = null;
65 | EsptouchTask task = new EsptouchTask(ssid, bssid, password, aes, context);
66 | ```
67 |
68 | ## v0.3.6.1
69 | - Modify bssid post sequence
70 |
71 | ## v0.3.6.0
72 | - Fix bug that cann't configure Chinese SSID
73 |
74 | ## v0.3.4.7
75 | - Change project from Eclipse to Android Studio
76 | - Modify app theme
77 | - Listen the change of wifi state when configuring
78 | - Support AES128 encryption
79 | ```
80 | byte[] secretKey = "1234567890123456".getBytes(); // TODO use your own key
81 | EspAES aes = new EspAES(secretKey);
82 | EsptouchTask task = new EsptouchTask(apSsid, apBssid, apPassword, aes, context);
83 | ```
84 |
85 | ## v0.3.4.6
86 | - isSsidHidden is true forever
87 | - Espressif's Smart Config is updated to v2.4.
88 | - Esptouch v0.3.4.6 support latest Espressif's Smart Config
89 |
90 | ## v0.3.4.5
91 | - fix the bug when interrupt the esptouch task, it will interrupt the current Thread instead of esptouch task. (thx for the engineer in Opple XingZhiGong's discovery)
92 | - support various encoding Wi-Fi Ssid not merely UTF-8 or other specified one
93 | - Espressif's Smart Config is updated to v2.4.
94 | - Esptouch v0.3.4.5 only support Espressif's Smart Config v2.4
95 |
96 | ## v0.3.4.3
97 | - Espressif's Smart Config is updated to v2.4.
98 | - Esptouch v0.3.4.3 only support Espressif's Smart Config v2.4
99 |
100 | ## v0.3.4.2
101 | - Espressif's Smart Config is updated to v2.4, and some paremeters are changed.
102 | - Esptouch v0.3.4.2 only support Espressif's Smart Config v2.4
103 | ```
104 | The usage of v0.3.4 is supported, besides one new API is added:
105 | void onEsptouchResultAdded(IEsptouchResult result);
106 | It support asyn callback when one device is connected to AP.
107 | ```
108 |
109 | ## v0.3.4
110 | - Espressif's Smart Config is updated to v2.4, and some paremeters are changed.
111 | - Esptouch v0.3.4 only support Espressif's Smart Config v2.4
112 |
113 | ## v0.3.3
114 | - Espressif's Smart Config is updated to v2.2, and the protocol is changed.
115 | - Esptouch v0.3.3 only support Espressif's Smart Config v2.2
116 | ```
117 | The usage of v0.3.0 is supported, besides one new API is added:
118 | List executeForResults(int expectTaskResultCount)
119 | The only differece is that it return list, and require expectTaskResultCount
120 | ```
121 |
122 | ## v0.3.2
123 | - Espressif's Smart Config is updated to v2.2, and the protocol is changed.
124 | - Esptouch v0.3.2 only support Espressif's Smart Config v2.2
125 |
126 | ## v0.3.1
127 | - Espressif's Smart Config is updated to v2.1, and the protocol is changed.
128 | - Esptouch v0.3.1 only support Espressif's Smart Config v2.1
129 | - fix some bugs in v0.3.0
130 |
131 | ## v0.3.0
132 | - Espressif's Smart Config is updated to v2.1, and the protocol is changed.
133 | - Esptouch v0.3.0 only support Espressif's Smart Config v2.1
134 | ```
135 | // build esptouch task
136 | String apSsid = "wifi-1;
137 | String apBssid = "12:34:56:78:9a:bc";
138 | String apPwd = "1234567890";
139 | boolean isSsidHidden = false;// whether the Ap's ssid is hidden, it is false usually
140 | IEspTouchTask task = new EspTouchTask(apSsid, apBssid, apPassword,
141 | isSsidHidden, XXXActivity.this);
142 | // if you'd like to determine the timeout by yourself, use the follow:
143 | int timeoutMillisecond = 58000;// it should >= 18000, 58000 is default
144 | IEspTouchTask task = new EspTouchTask(apSsid, apBssid, apPassword,
145 | isSsidHidden, timeoutMillisecond, XXXActivity.this);
146 | // execute for result
147 | IESPTouchResult esptouchReult = task.executeForResult();
148 | // note: one task can't executed more than once:
149 | IESPTouchTask esptouchTask = new EsptouchTask(...)
150 | // wrong usage, which shouldn't happen
151 | {
152 | esptouchTask.executeForResult();
153 | esptouchTask.executeForResult();
154 | }
155 | // correct usage
156 | {
157 | esptouchTask.executeForResult();
158 | IEsptouchTask esptouchTask = new EsptouchTask(...);
159 | esptouchTask.executeForResult();
160 | }
161 | ```
162 |
163 | ## v0.2.2
164 | - add isCancelled API in ESPTouchTask and ESPTouchResult to check whether the task is cancelled by user directly.
165 |
166 | ## v0.2.1
167 | - fix the bug when SSID char is more than one byte value(0xff), the apk will crash
168 | - thx for the engineer in NATop YoungYang's discovery
169 | - the encoding charset could be set, the default one is "UTF-8"
170 | - change the constant ESPTOUCH_ENCODING_CHARSET in ByteUtil.java
171 | - It will lead to ESPTOUCH fail for wrong CHARSET is set. Whether the CHARSET is correct is depend on the phone or pad.
172 | - More info and discussion please refer to http://bbs.espressif.com/viewtopic.php?f=8&t=397)
173 |
174 | ## v0.2.0
175 | - add check valid mechanism to forbid such situation
176 | ```
177 | String apSsid = "";// or apSsid = null
178 | String apPassword = "pwd";
179 | IEsptouchTask esptouchTask = new EsptouchTask(apSsid, apPassword);
180 | ```
181 | - add check whether the task is executed to forbid such situation
182 | - thx for the engineer in smartline YuguiYu's proposal:
183 | ```
184 | String apSsid = "ssid";
185 | String apPassword = "pwd";
186 | IEsptouchTask esptouchTask = new EsptouchTask(apSsid, apPassword);
187 | // wrong usage, which shouldn't happen
188 | {
189 | esptouchTask.execute();
190 | esptouchTask.execute();
191 | }
192 | // correct usage
193 | {
194 | esptouchTask.execute();
195 | esptouchTask = new EsptouchTask(apSsid, apPassword);
196 | esptouchTask.execute();
197 | }
198 | ```
199 |
200 | ## v0.1.9
201 | - fix the bug that some Android device can't receive broadcast
202 | - thx for the engineer in Joyoung xushx's help
203 | - fix some old bugs in the App
204 | - Add new Interface of Esptouch task( Smart Configure must v1.1 to support it)
205 | ```
206 | // create the Esptouch task
207 | IEsptouchTask esptouchTask = new EsptouchTask(apSsid, apPassword);
208 | // execute syn util it suc or timeout
209 | IEsptouchResult result = esptouchTask.executeForResult();
210 | // check whehter the execute is suc
211 | boolean isSuc = result.isSuc();
212 | // get the device's bssid, the format of the bssid is like this format: "18fe3497f310"
213 | String bssid = result.getBssid();
214 | // when you'd like to interrupt it, just call the method below, and esptouchTask.execute() will return false after it:
215 | esptouchTask.interrupt();
216 | ```
217 |
218 | ## v0.1.7
219 | - The entrance of the Demo is com.espressif.iot.esptouch.demo_activity.EsptouchDemoActivity.java
220 | - IEsptouchTask is the interface of Esptouch task.
221 | ```
222 | // create the Esptouch task
223 | IEsptouchTask esptouchTask = new EsptouchTask(apSsid, apPassword);
224 | // execute syn util it suc or timeout
225 | boolean result = esptouchTask.execute();
226 | // when you'd like to interrupt it, just call the method below, and esptouchTask.execute() will return false after it:
227 | esptouchTask.interrupt();
228 | ```
229 | - The abstract interface is in the package com.espressif.iot.esptouch
230 | - More info about the EspTouch Demo, please read the source code and annotation
231 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_esptouch2.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
27 |
28 |
38 |
39 |
51 |
52 |
62 |
63 |
75 |
76 |
86 |
87 |
96 |
97 |
103 |
104 |
105 |
113 |
114 |
120 |
121 |
122 |
131 |
132 |
138 |
139 |
140 |
147 |
148 |
152 |
153 |
159 |
160 |
167 |
168 |
174 |
175 |
176 |
177 |
185 |
186 |
191 |
192 |
193 |
202 |
203 |
209 |
210 |
220 |
221 |
222 |
223 |
224 |
--------------------------------------------------------------------------------
/app/src/main/java/com/espressif/esptouch/android/v2/EspTouch2Activity.java:
--------------------------------------------------------------------------------
1 | package com.espressif.esptouch.android.v2;
2 |
3 | import android.Manifest;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.util.Log;
7 | import android.view.View;
8 |
9 | import androidx.activity.result.ActivityResultLauncher;
10 | import androidx.activity.result.contract.ActivityResultContracts;
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 | import androidx.appcompat.app.ActionBar;
14 | import androidx.core.app.ActivityCompat;
15 |
16 | import com.espressif.esptouch.android.EspTouchActivityAbs;
17 | import com.espressif.esptouch.android.EspTouchApp;
18 | import com.espressif.esptouch.android.R;
19 | import com.espressif.esptouch.android.databinding.ActivityEsptouch2Binding;
20 | import com.espressif.iot.esptouch2.provision.EspProvisioner;
21 | import com.espressif.iot.esptouch2.provision.EspProvisioningRequest;
22 | import com.espressif.iot.esptouch2.provision.EspSyncListener;
23 | import com.espressif.iot.esptouch2.provision.IEspProvisioner;
24 | import com.espressif.iot.esptouch2.provision.TouchNetUtil;
25 |
26 | import java.lang.ref.WeakReference;
27 | import java.net.InetAddress;
28 |
29 | public class EspTouch2Activity extends EspTouchActivityAbs {
30 | private static final String TAG = EspTouch2Activity.class.getSimpleName();
31 |
32 | private static final int REQUEST_PERMISSION = 0x01;
33 |
34 | private EspProvisioner mProvisioner;
35 |
36 | private ActivityEsptouch2Binding mBinding;
37 |
38 | private InetAddress mAddress;
39 | private String mSsid;
40 | private byte[] mSsidBytes;
41 | private String mBssid;
42 | private CharSequence mMessage;
43 | private int mMessageVisible;
44 | private int mControlVisible;
45 |
46 | private ActivityResultLauncher mProvisionLauncher;
47 |
48 | @Override
49 | protected void onCreate(@Nullable Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 |
52 | mBinding = ActivityEsptouch2Binding.inflate(getLayoutInflater());
53 | setContentView(mBinding.getRoot());
54 |
55 | mProvisionLauncher = registerForActivityResult(
56 | new ActivityResultContracts.StartActivityForResult(),
57 | result -> mBinding.confirmBtn.setEnabled(true)
58 | );
59 |
60 | mBinding.controlGroup.setVisibility(View.INVISIBLE);
61 | mBinding.confirmBtn.setOnClickListener(v -> {
62 | if (launchProvisioning()) {
63 | mBinding.confirmBtn.setEnabled(false);
64 | }
65 | });
66 |
67 | ActionBar actionBar = getSupportActionBar();
68 | if (actionBar != null) {
69 | actionBar.setDisplayHomeAsUpEnabled(true);
70 | }
71 |
72 | EspTouchApp.getInstance().observeBroadcast(this, action -> check());
73 |
74 | check();
75 | }
76 |
77 | @Override
78 | protected void onDestroy() {
79 | super.onDestroy();
80 | }
81 |
82 | @Override
83 | protected void onStart() {
84 | super.onStart();
85 |
86 | mProvisioner = new EspProvisioner(getApplicationContext());
87 | SyncListener syncListener = new SyncListener(mProvisioner);
88 | mProvisioner.startSync(syncListener);
89 | }
90 |
91 | @Override
92 | protected void onStop() {
93 | super.onStop();
94 |
95 | if (mProvisioner != null) {
96 | mProvisioner.stopSync();
97 | mProvisioner.close();
98 | mProvisioner = null;
99 | }
100 | }
101 |
102 | @Override
103 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
104 | if (requestCode == REQUEST_PERMISSION) {
105 | check();
106 | return;
107 | }
108 |
109 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
110 | }
111 |
112 | @Override
113 | protected String getEspTouchVersion() {
114 | return getString(R.string.esptouch2_about_version, IEspProvisioner.ESPTOUCH_VERSION);
115 | }
116 |
117 | private boolean launchProvisioning() {
118 | EspProvisioningRequest request = genRequest();
119 | if (request == null) {
120 | return false;
121 | }
122 | if (mProvisioner != null) {
123 | mProvisioner.close();
124 | }
125 |
126 | Intent intent = new Intent(EspTouch2Activity.this, EspProvisioningActivity.class);
127 | intent.putExtra(EspProvisioningActivity.KEY_PROVISION_REQUEST, request);
128 | intent.putExtra(EspProvisioningActivity.KEY_DEVICE_COUNT, getDeviceCount());
129 | mProvisionLauncher.launch(intent);
130 |
131 | return true;
132 | }
133 |
134 | private boolean checkEnable() {
135 | StateResult stateResult = checkState();
136 | if (!stateResult.permissionGranted) {
137 | mMessage = stateResult.message;
138 | mBinding.messageView.setOnClickListener(v -> {
139 | String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION};
140 | ActivityCompat.requestPermissions(EspTouch2Activity.this, permissions, REQUEST_PERMISSION);
141 | });
142 | return false;
143 | }
144 |
145 | mBinding.messageView.setOnClickListener(null);
146 |
147 | mSsid = stateResult.ssid;
148 | mSsidBytes = stateResult.ssidBytes;
149 | mBssid = stateResult.bssid;
150 | mMessage = stateResult.message;
151 | mAddress = stateResult.address;
152 |
153 | if (stateResult.wifiConnected && stateResult.is5G) {
154 | mBinding.hintView.setText(R.string.esptouch_message_wifi_frequency);
155 | } else {
156 | mBinding.hintView.setText("");
157 | }
158 |
159 | return stateResult.wifiConnected;
160 | }
161 |
162 | private byte[] getBssidBytes() {
163 | return mBssid == null ? null : TouchNetUtil.convertBssid2Bytes(mBssid);
164 | }
165 |
166 | private void invalidateAll() {
167 | mBinding.controlGroup.setVisibility(mControlVisible);
168 | mBinding.apSsidText.setText(mSsid);
169 | mBinding.apBssidText.setText(mBssid);
170 | mBinding.ipText.setText(mAddress == null ? "" : mAddress.getHostAddress());
171 | mBinding.messageView.setText(mMessage);
172 | mBinding.messageView.setVisibility(mMessageVisible);
173 | }
174 |
175 | private void check() {
176 | if (checkEnable()) {
177 | mControlVisible = View.VISIBLE;
178 | mMessageVisible = View.GONE;
179 | } else {
180 | mControlVisible = View.GONE;
181 | mMessageVisible = View.VISIBLE;
182 |
183 | if (mProvisioner != null) {
184 | if (mProvisioner.isSyncing()) {
185 | mProvisioner.stopSync();
186 | }
187 | if (mProvisioner.isProvisioning()) {
188 | mProvisioner.stopProvisioning();
189 | }
190 | }
191 | }
192 | invalidateAll();
193 | }
194 |
195 | private EspProvisioningRequest genRequest() {
196 | mBinding.aesKeyLayout.setError(null);
197 | mBinding.customDataLayout.setError(null);
198 |
199 | CharSequence aesKeyChars = mBinding.aesKeyEdit.getText();
200 | byte[] aesKey = null;
201 | if (aesKeyChars != null && aesKeyChars.length() > 0) {
202 | aesKey = aesKeyChars.toString().getBytes();
203 | }
204 | if (aesKey != null && aesKey.length != 16) {
205 | mBinding.aesKeyLayout.setError(getString(R.string.esptouch2_aes_key_error));
206 | return null;
207 | }
208 |
209 | int currentCheckedSecurityId = mBinding.securityVerGroup.getCheckedRadioButtonId();
210 | int securityVer;
211 | if (currentCheckedSecurityId == R.id.securityV1) {
212 | securityVer = EspProvisioningRequest.SECURITY_V1;
213 | } else if (currentCheckedSecurityId == R.id.securityV2) {
214 | securityVer = EspProvisioningRequest.SECURITY_V2;
215 | } else {
216 | securityVer = EspProvisioningRequest.SECURITY_V1;
217 | }
218 |
219 | CharSequence customDataChars = mBinding.customDataEdit.getText();
220 | byte[] customData = null;
221 | if (customDataChars != null && customDataChars.length() > 0) {
222 | customData = customDataChars.toString().getBytes();
223 | }
224 | int customDataMaxLen = EspProvisioningRequest.RESERVED_LENGTH_MAX;
225 | if (customData != null && customData.length > customDataMaxLen) {
226 | mBinding.customDataLayout.setError(getString(R.string.esptouch2_custom_data_error, customDataMaxLen));
227 | return null;
228 | }
229 |
230 | CharSequence password = mBinding.apPasswordEdit.getText();
231 | return new EspProvisioningRequest.Builder(getApplicationContext())
232 | .setSSID(mSsidBytes)
233 | .setBSSID(getBssidBytes())
234 | .setPassword(password == null ? null : password.toString().getBytes())
235 | .setAESKey(aesKey)
236 | .setSecurityVer(securityVer)
237 | .setReservedData(customData)
238 | .build();
239 | }
240 |
241 | private int getDeviceCount() {
242 | CharSequence deviceCountStr = mBinding.deviceCountEdit.getText();
243 | int deviceCount = -1;
244 | if (deviceCountStr != null && deviceCountStr.length() > 0) {
245 | try {
246 | deviceCount = Integer.parseInt(deviceCountStr.toString());
247 | } catch (Exception e) {
248 | Log.w(TAG, "getDeviceCount: ", e);
249 | }
250 | }
251 | return deviceCount;
252 | }
253 |
254 | private static class SyncListener implements EspSyncListener {
255 | private final WeakReference provisioner;
256 |
257 | SyncListener(EspProvisioner provisioner) {
258 | this.provisioner = new WeakReference<>(provisioner);
259 | }
260 |
261 | @Override
262 | public void onStart() {
263 | Log.d(TAG, "SyncListener onStart");
264 | }
265 |
266 | @Override
267 | public void onStop() {
268 | Log.d(TAG, "SyncListener onStop");
269 | }
270 |
271 | @Override
272 | public void onError(Exception e) {
273 | Log.w(TAG, "onError: ", e);
274 | EspProvisioner provisioner = this.provisioner.get();
275 | if (provisioner != null) {
276 | provisioner.stopSync();
277 | }
278 | }
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/esptouch/src/main/java/com/espressif/iot/esptouch/util/ByteUtil.java:
--------------------------------------------------------------------------------
1 | package com.espressif.iot.esptouch.util;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.util.Random;
5 |
6 | /**
7 | * In Java, it don't support unsigned int, so we use char to replace uint8.
8 | * The range of byte is [-128,127], and the range of char is [0,65535].
9 | * So the byte could used to store the uint8.
10 | * (We assume that the String could be mapped to assic)
11 | *
12 | * @author afunx
13 | */
14 | public class ByteUtil {
15 |
16 | public static final String ESPTOUCH_ENCODING_CHARSET = "UTF-8";
17 |
18 | /**
19 | * Put String to byte[]
20 | *
21 | * @param destbytes the byte[] of dest
22 | * @param srcString the String of src
23 | * @param destOffset the offset of byte[]
24 | * @param srcOffset the offset of String
25 | * @param count the count of dest, and the count of src as well
26 | */
27 | public static void putString2bytes(byte[] destbytes, String srcString,
28 | int destOffset, int srcOffset, int count) {
29 | for (int i = 0; i < count; i++) {
30 | destbytes[count + i] = srcString.getBytes()[i];
31 | }
32 | }
33 |
34 | /**
35 | * Convert uint8 into char( we treat char as uint8)
36 | *
37 | * @param uint8 the unit8 to be converted
38 | * @return the byte of the unint8
39 | */
40 | public static byte convertUint8toByte(char uint8) {
41 | if (uint8 > Byte.MAX_VALUE - Byte.MIN_VALUE) {
42 | throw new RuntimeException("Out of Boundary");
43 | }
44 | return (byte) uint8;
45 | }
46 |
47 | /**
48 | * Convert char into uint8( we treat char as uint8 )
49 | *
50 | * @param b the byte to be converted
51 | * @return the char(uint8)
52 | */
53 | public static char convertByte2Uint8(byte b) {
54 | // char will be promoted to int for char don't support & operator
55 | // & 0xff could make negatvie value to positive
56 | return (char) (b & 0xff);
57 | }
58 |
59 | /**
60 | * Convert byte[] into char[]( we treat char[] as uint8[])
61 | *
62 | * @param bytes the byte[] to be converted
63 | * @return the char[](uint8[])
64 | */
65 | public static char[] convertBytes2Uint8s(byte[] bytes) {
66 | int len = bytes.length;
67 | char[] uint8s = new char[len];
68 | for (int i = 0; i < len; i++) {
69 | uint8s[i] = convertByte2Uint8(bytes[i]);
70 | }
71 | return uint8s;
72 | }
73 |
74 | /**
75 | * Put byte[] into char[]( we treat char[] as uint8[])
76 | *
77 | * @param destUint8s the char[](uint8[]) array
78 | * @param srcBytes the byte[]
79 | * @param destOffset the offset of char[](uint8[])
80 | * @param srcOffset the offset of byte[]
81 | * @param count the count of dest, and the count of src as well
82 | */
83 | public static void putbytes2Uint8s(char[] destUint8s, byte[] srcBytes,
84 | int destOffset, int srcOffset, int count) {
85 | for (int i = 0; i < count; i++) {
86 | destUint8s[destOffset + i] = convertByte2Uint8(srcBytes[srcOffset
87 | + i]);
88 | }
89 | }
90 |
91 | /**
92 | * Convert byte to Hex String
93 | *
94 | * @param b the byte to be converted
95 | * @return the Hex String
96 | */
97 | public static String convertByte2HexString(byte b) {
98 | char u8 = convertByte2Uint8(b);
99 | return Integer.toHexString(u8);
100 | }
101 |
102 | /**
103 | * Convert char(uint8) to Hex String
104 | *
105 | * @param u8 the char(uint8) to be converted
106 | * @return the Hex String
107 | */
108 | public static String convertU8ToHexString(char u8) {
109 | return Integer.toHexString(u8);
110 | }
111 |
112 | /**
113 | * Split uint8 to 2 bytes of high byte and low byte. e.g. 20 = 0x14 should
114 | * be split to [0x01,0x04] 0x01 is high byte and 0x04 is low byte
115 | *
116 | * @param uint8 the char(uint8)
117 | * @return the high and low bytes be split, byte[0] is high and byte[1] is
118 | * low
119 | */
120 | public static byte[] splitUint8To2bytes(char uint8) {
121 | if (uint8 < 0 || uint8 > 0xff) {
122 | throw new RuntimeException("Out of Boundary");
123 | }
124 | String hexString = Integer.toHexString(uint8);
125 | byte low;
126 | byte high;
127 | if (hexString.length() > 1) {
128 | high = (byte) Integer.parseInt(hexString.substring(0, 1), 16);
129 | low = (byte) Integer.parseInt(hexString.substring(1, 2), 16);
130 | } else {
131 | high = 0;
132 | low = (byte) Integer.parseInt(hexString.substring(0, 1), 16);
133 | }
134 | byte[] result = new byte[]{high, low};
135 | return result;
136 | }
137 |
138 | /**
139 | * Combine 2 bytes (high byte and low byte) to one whole byte
140 | *
141 | * @param high the high byte
142 | * @param low the low byte
143 | * @return the whole byte
144 | */
145 | public static byte combine2bytesToOne(byte high, byte low) {
146 | if (high < 0 || high > 0xf || low < 0 || low > 0xf) {
147 | throw new RuntimeException("Out of Boundary");
148 | }
149 | return (byte) (high << 4 | low);
150 | }
151 |
152 | /**
153 | * Combine 2 bytes (high byte and low byte) to
154 | *
155 | * @param high the high byte
156 | * @param low the low byte
157 | * @return the char(u8)
158 | */
159 | public static char combine2bytesToU16(byte high, byte low) {
160 | char highU8 = convertByte2Uint8(high);
161 | char lowU8 = convertByte2Uint8(low);
162 | return (char) (highU8 << 8 | lowU8);
163 | }
164 |
165 | /**
166 | * Generate the random byte to be sent
167 | *
168 | * @return the random byte
169 | */
170 | private static byte randomByte() {
171 | return (byte) (127 - new Random().nextInt(256));
172 | }
173 |
174 | /**
175 | * Generate the random byte to be sent
176 | *
177 | * @param len the len presented by u8
178 | * @return the byte[] to be sent
179 | */
180 | public static byte[] randomBytes(char len) {
181 | byte[] data = new byte[len];
182 | for (int i = 0; i < len; i++) {
183 | data[i] = randomByte();
184 | }
185 | return data;
186 | }
187 |
188 | public static byte[] genSpecBytes(char len) {
189 | byte[] data = new byte[len];
190 | for (int i = 0; i < len; i++) {
191 | data[i] = '1';
192 | }
193 | return data;
194 | }
195 |
196 | /**
197 | * Generate the random byte to be sent
198 | *
199 | * @param len the len presented by byte
200 | * @return the byte[] to be sent
201 | */
202 | public static byte[] randomBytes(byte len) {
203 | char u8 = convertByte2Uint8(len);
204 | return randomBytes(u8);
205 | }
206 |
207 | /**
208 | * Generate the specific byte to be sent
209 | *
210 | * @param len the len presented by byte
211 | * @return the byte[]
212 | */
213 | public static byte[] genSpecBytes(byte len) {
214 | char u8 = convertByte2Uint8(len);
215 | return genSpecBytes(u8);
216 | }
217 |
218 | public static String parseBssid(byte[] bssidBytes, int offset, int count) {
219 | byte[] bytes = new byte[count];
220 | System.arraycopy(bssidBytes, offset, bytes, 0, count);
221 | return parseBssid(bytes);
222 | }
223 |
224 | /**
225 | * parse "24,-2,52,-102,-93,-60" to "18,fe,34,9a,a3,c4"
226 | * parse the bssid from hex to String
227 | *
228 | * @param bssidBytes the hex bytes bssid, e.g. {24,-2,52,-102,-93,-60}
229 | * @return the String of bssid, e.g. 18fe349aa3c4
230 | */
231 | public static String parseBssid(byte[] bssidBytes) {
232 | StringBuilder sb = new StringBuilder();
233 | int k;
234 | String hexK;
235 | String str;
236 | for (byte bssidByte : bssidBytes) {
237 | k = 0xff & bssidByte;
238 | hexK = Integer.toHexString(k);
239 | str = ((k < 16) ? ("0" + hexK) : (hexK));
240 | sb.append(str);
241 | }
242 | return sb.toString();
243 | }
244 |
245 | /**
246 | * @param string the string to be used
247 | * @return the byte[] of String according to {@link #ESPTOUCH_ENCODING_CHARSET}
248 | */
249 | public static byte[] getBytesByString(String string) {
250 | try {
251 | return string.getBytes(ESPTOUCH_ENCODING_CHARSET);
252 | } catch (UnsupportedEncodingException e) {
253 | throw new IllegalArgumentException("the charset is invalid");
254 | }
255 | }
256 |
257 | private static void test_splitUint8To2bytes() {
258 | // 20 = 0x14
259 | byte[] result = splitUint8To2bytes((char) 20);
260 | if (result[0] == 1 && result[1] == 4) {
261 | System.out.println("test_splitUint8To2bytes(): pass");
262 | } else {
263 | System.out.println("test_splitUint8To2bytes(): fail");
264 | }
265 | }
266 |
267 | private static void test_combine2bytesToOne() {
268 | byte high = 0x01;
269 | byte low = 0x04;
270 | if (combine2bytesToOne(high, low) == 20) {
271 | System.out.println("test_combine2bytesToOne(): pass");
272 | } else {
273 | System.out.println("test_combine2bytesToOne(): fail");
274 | }
275 | }
276 |
277 | private static void test_convertChar2Uint8() {
278 | byte b1 = 'a';
279 | // -128: 1000 0000 should be 128 in unsigned char
280 | // -1: 1111 1111 should be 255 in unsigned char
281 | byte b2 = (byte) -128;
282 | byte b3 = (byte) -1;
283 | if (convertByte2Uint8(b1) == 97 && convertByte2Uint8(b2) == 128
284 | && convertByte2Uint8(b3) == 255) {
285 | System.out.println("test_convertChar2Uint8(): pass");
286 | } else {
287 | System.out.println("test_convertChar2Uint8(): fail");
288 | }
289 | }
290 |
291 | private static void test_convertUint8toByte() {
292 | char c1 = 'a';
293 | // 128: 1000 0000 should be -128 in byte
294 | // 255: 1111 1111 should be -1 in byte
295 | char c2 = 128;
296 | char c3 = 255;
297 | if (convertUint8toByte(c1) == 97 && convertUint8toByte(c2) == -128
298 | && convertUint8toByte(c3) == -1) {
299 | System.out.println("test_convertUint8toByte(): pass");
300 | } else {
301 | System.out.println("test_convertUint8toByte(): fail");
302 | }
303 | }
304 |
305 | private static void test_parseBssid() {
306 | byte b[] = {15, -2, 52, -102, -93, -60};
307 | if (parseBssid(b).equals("0ffe349aa3c4")) {
308 | System.out.println("test_parseBssid(): pass");
309 | } else {
310 | System.out.println("test_parseBssid(): fail");
311 | }
312 | }
313 |
314 | public static void main(String args[]) {
315 | test_convertUint8toByte();
316 | test_convertChar2Uint8();
317 | test_splitUint8To2bytes();
318 | test_combine2bytesToOne();
319 | test_parseBssid();
320 | }
321 |
322 | }
323 |
--------------------------------------------------------------------------------
/app/src/main/java/com/espressif/esptouch/android/v1/EspTouchActivity.java:
--------------------------------------------------------------------------------
1 | package com.espressif.esptouch.android.v1;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 | import android.os.AsyncTask;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.util.Log;
10 | import android.view.View;
11 | import android.widget.Toast;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.appcompat.app.ActionBar;
15 | import androidx.appcompat.app.AlertDialog;
16 |
17 | import com.espressif.esptouch.android.EspTouchActivityAbs;
18 | import com.espressif.esptouch.android.EspTouchApp;
19 | import com.espressif.esptouch.android.R;
20 | import com.espressif.esptouch.android.databinding.ActivityEsptouchBinding;
21 | import com.espressif.iot.esptouch.EsptouchTask;
22 | import com.espressif.iot.esptouch.IEsptouchResult;
23 | import com.espressif.iot.esptouch.IEsptouchTask;
24 | import com.espressif.iot.esptouch.util.ByteUtil;
25 | import com.espressif.iot.esptouch.util.TouchNetUtil;
26 |
27 | import java.lang.ref.WeakReference;
28 | import java.util.ArrayList;
29 | import java.util.List;
30 | import java.util.Locale;
31 |
32 | public class EspTouchActivity extends EspTouchActivityAbs {
33 | private static final String TAG = EspTouchActivity.class.getSimpleName();
34 |
35 | private static final int REQUEST_PERMISSION = 0x01;
36 |
37 | private EsptouchAsyncTask4 mTask;
38 |
39 | private ActivityEsptouchBinding mBinding;
40 |
41 | private String mSsid;
42 | private byte[] mSsidBytes;
43 | private String mBssid;
44 |
45 | @Override
46 | protected void onCreate(Bundle savedInstanceState) {
47 | super.onCreate(savedInstanceState);
48 | mBinding = ActivityEsptouchBinding.inflate(getLayoutInflater());
49 | setContentView(mBinding.getRoot());
50 | mBinding.confirmBtn.setOnClickListener(v -> executeEsptouch());
51 |
52 | mBinding.cancelButton.setOnClickListener(v -> {
53 | showProgress(false);
54 | if (mTask != null) {
55 | mTask.cancelEsptouch();
56 | }
57 | });
58 |
59 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
60 | String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION};
61 | requestPermissions(permissions, REQUEST_PERMISSION);
62 | }
63 |
64 | EspTouchApp.getInstance().observeBroadcast(this, broadcast -> {
65 | Log.d(TAG, "onCreate: Broadcast=" + broadcast);
66 | onWifiChanged();
67 | });
68 |
69 | ActionBar actionBar = getSupportActionBar();
70 | if (actionBar != null) {
71 | actionBar.setDisplayHomeAsUpEnabled(true);
72 | }
73 | }
74 |
75 | @Override
76 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
77 | if (requestCode == REQUEST_PERMISSION) {
78 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
79 | onWifiChanged();
80 | } else {
81 | new AlertDialog.Builder(this)
82 | .setTitle(R.string.esptouch1_location_permission_title)
83 | .setMessage(R.string.esptouch1_location_permission_message)
84 | .setCancelable(false)
85 | .setPositiveButton(android.R.string.ok, (dialog, which) -> finish())
86 | .show();
87 | }
88 |
89 | return;
90 | }
91 |
92 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
93 | }
94 |
95 | private void showProgress(boolean show) {
96 | if (show) {
97 | mBinding.content.setVisibility(View.INVISIBLE);
98 | mBinding.progressView.setVisibility(View.VISIBLE);
99 | } else {
100 | mBinding.content.setVisibility(View.VISIBLE);
101 | mBinding.progressView.setVisibility(View.GONE);
102 | }
103 | }
104 |
105 | @Override
106 | protected String getEspTouchVersion() {
107 | return getString(R.string.esptouch1_about_version, IEsptouchTask.ESPTOUCH_VERSION);
108 | }
109 |
110 | private void onWifiChanged() {
111 | StateResult stateResult = checkState();
112 | mSsid = stateResult.ssid;
113 | mSsidBytes = stateResult.ssidBytes;
114 | mBssid = stateResult.bssid;
115 | CharSequence message = stateResult.message;
116 | boolean confirmEnable = false;
117 | if (stateResult.wifiConnected) {
118 | confirmEnable = true;
119 | if (stateResult.is5G) {
120 | message = getString(R.string.esptouch_message_wifi_frequency);
121 | }
122 | } else {
123 | if (mTask != null) {
124 | mTask.cancelEsptouch();
125 | mTask = null;
126 | new AlertDialog.Builder(EspTouchActivity.this)
127 | .setMessage(R.string.esptouch1_configure_wifi_change_message)
128 | .setNegativeButton(android.R.string.cancel, null)
129 | .show();
130 | }
131 | }
132 |
133 | mBinding.apSsidText.setText(mSsid);
134 | mBinding.apBssidText.setText(mBssid);
135 | mBinding.messageView.setText(message);
136 | mBinding.confirmBtn.setEnabled(confirmEnable);
137 | }
138 |
139 | private void executeEsptouch() {
140 | byte[] ssid = mSsidBytes == null ? ByteUtil.getBytesByString(this.mSsid)
141 | : mSsidBytes;
142 | CharSequence pwdStr = mBinding.apPasswordEdit.getText();
143 | byte[] password = pwdStr == null ? null : ByteUtil.getBytesByString(pwdStr.toString());
144 | byte[] bssid = TouchNetUtil.parseBssid2bytes(this.mBssid);
145 | CharSequence devCountStr = mBinding.deviceCountEdit.getText();
146 | byte[] deviceCount = devCountStr == null ? new byte[0] : devCountStr.toString().getBytes();
147 | byte[] broadcast = {(byte) (mBinding.packageModeGroup.getCheckedRadioButtonId() == R.id.packageBroadcast
148 | ? 1 : 0)};
149 |
150 | if (mTask != null) {
151 | mTask.cancelEsptouch();
152 | }
153 | mTask = new EsptouchAsyncTask4(this);
154 | mTask.execute(ssid, bssid, password, deviceCount, broadcast);
155 | }
156 |
157 | private static class EsptouchAsyncTask4 extends AsyncTask> {
158 | private final WeakReference mActivity;
159 |
160 | private final Object mLock = new Object();
161 | private AlertDialog mResultDialog;
162 | private IEsptouchTask mEsptouchTask;
163 |
164 | EsptouchAsyncTask4(EspTouchActivity activity) {
165 | mActivity = new WeakReference<>(activity);
166 | }
167 |
168 | void cancelEsptouch() {
169 | cancel(true);
170 | EspTouchActivity activity = mActivity.get();
171 | if (activity != null) {
172 | activity.showProgress(false);
173 | }
174 | if (mResultDialog != null) {
175 | mResultDialog.dismiss();
176 | }
177 | if (mEsptouchTask != null) {
178 | mEsptouchTask.interrupt();
179 | }
180 | }
181 |
182 | @Override
183 | protected void onPreExecute() {
184 | EspTouchActivity activity = mActivity.get();
185 | activity.mBinding.testResult.setText("");
186 | activity.showProgress(true);
187 | }
188 |
189 | @Override
190 | protected void onProgressUpdate(IEsptouchResult... values) {
191 | EspTouchActivity activity = mActivity.get();
192 | if (activity != null) {
193 | IEsptouchResult result = values[0];
194 | Log.i(TAG, "EspTouchResult: " + result);
195 | String text = result.getBssid() + " is connected to the wifi";
196 | Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
197 |
198 | activity.mBinding.testResult.append(String.format(
199 | Locale.ENGLISH,
200 | "%s,%s\n",
201 | result.getInetAddress().getHostAddress(),
202 | result.getBssid()
203 | ));
204 | }
205 | }
206 |
207 | @Override
208 | protected List doInBackground(byte[]... params) {
209 | EspTouchActivity activity = mActivity.get();
210 | int taskResultCount;
211 | synchronized (mLock) {
212 | byte[] apSsid = params[0];
213 | byte[] apBssid = params[1];
214 | byte[] apPassword = params[2];
215 | byte[] deviceCountData = params[3];
216 | byte[] broadcastData = params[4];
217 | taskResultCount = deviceCountData.length == 0 ? -1 : Integer.parseInt(new String(deviceCountData));
218 | Context context = activity.getApplicationContext();
219 | mEsptouchTask = new EsptouchTask(apSsid, apBssid, apPassword, context);
220 | mEsptouchTask.setPackageBroadcast(broadcastData[0] == 1);
221 | mEsptouchTask.setEsptouchListener(this::publishProgress);
222 | }
223 | return mEsptouchTask.executeForResults(taskResultCount);
224 | }
225 |
226 | @Override
227 | protected void onPostExecute(List result) {
228 | EspTouchActivity activity = mActivity.get();
229 | activity.mTask = null;
230 | activity.showProgress(false);
231 | if (result == null) {
232 | mResultDialog = new AlertDialog.Builder(activity)
233 | .setMessage(R.string.esptouch1_configure_result_failed_port)
234 | .setPositiveButton(android.R.string.ok, null)
235 | .show();
236 | mResultDialog.setCanceledOnTouchOutside(false);
237 | return;
238 | }
239 |
240 | // check whether the task is cancelled and no results received
241 | IEsptouchResult firstResult = result.get(0);
242 | if (firstResult.isCancelled()) {
243 | return;
244 | }
245 | // the task received some results including cancelled while
246 | // executing before receiving enough results
247 |
248 | if (!firstResult.isSuc()) {
249 | mResultDialog = new AlertDialog.Builder(activity)
250 | .setMessage(R.string.esptouch1_configure_result_failed)
251 | .setPositiveButton(android.R.string.ok, null)
252 | .show();
253 | mResultDialog.setCanceledOnTouchOutside(false);
254 | return;
255 | }
256 |
257 | ArrayList resultMsgList = new ArrayList<>(result.size());
258 | for (IEsptouchResult touchResult : result) {
259 | String message = activity.getString(R.string.esptouch1_configure_result_success_item,
260 | touchResult.getBssid(), touchResult.getInetAddress().getHostAddress());
261 | resultMsgList.add(message);
262 | }
263 | CharSequence[] items = new CharSequence[resultMsgList.size()];
264 | mResultDialog = new AlertDialog.Builder(activity)
265 | .setTitle(R.string.esptouch1_configure_result_success)
266 | .setItems(resultMsgList.toArray(items), null)
267 | .setPositiveButton(android.R.string.ok, null)
268 | .show();
269 | mResultDialog.setCanceledOnTouchOutside(false);
270 | }
271 | }
272 | }
273 |
--------------------------------------------------------------------------------