├── .gitignore ├── .idea ├── caches │ ├── build_file_checksums.ser │ └── gradle_models.ser ├── codeStyles │ └── Project.xml ├── compiler.xml ├── dictionaries │ └── rnd.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── README.md ├── YModemLibrary ├── .gitignore ├── build.gradle ├── libs │ └── jars │ │ └── ymodem.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bw │ │ └── yml │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── bw │ │ │ └── yml │ │ │ ├── CRC16.java │ │ │ ├── FileStreamThread.java │ │ │ ├── InputStreamSource.java │ │ │ ├── Lg.java │ │ │ ├── SourceScheme.java │ │ │ ├── TimeOutHelper.java │ │ │ ├── YModem.java │ │ │ ├── YModemListener.java │ │ │ └── YModemUtil.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── bw │ └── yml │ └── ExampleUnitTest.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bw │ │ └── ym │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── bw │ │ │ └── ym │ │ │ └── demo │ │ │ ├── ConnectThread.java │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── bw │ └── ym │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── settings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/caches/gradle_models.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/.idea/caches/gradle_models.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/dictionaries/rnd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YModemLib
2 | [![](https://jitpack.io/v/ArdWang/YModemLib.svg)](https://jitpack.io/#ArdWang/YModemLib) 3 |
4 | 5 | ---------------------- 6 | Ymodem Bluetooth communication protocol uses version 19+. What is indicated here is only a communication protocol. For specific operations, you need to check the Demo version. The version has been updated to v2.0.3 7 | 8 | Thank you LeonXtp for your help 9 | 10 | iOS Version https://github.com/ArdWang/YModemlib_iOS 11 | 12 | flutter https://github.com/QuickDevelopers/flutter_ymodem 13 | 14 | 15 | ### Update December 31 2024 16 | 17 | Modify error 18 | 19 | ```java 20 | 21 | When the build value is size=128, the sent header packet value is still 2, and it should be 1 22 | 23 | ``` 24 | 25 | 26 | 27 | ### Update September 26 2024 28 | 29 | You can choose between two options and pass empty。 30 | 31 | ``` 32 | 33 | yModem.start(null); 34 | 35 | or 36 | 37 | yModem.start(customData); 38 | 39 | ``` 40 | 41 | ### Update March 3/2024 42 | 43 | Added dynamic YModemUtil Data. 44 | 45 | add: new 46 | ```java 47 | String customData = "Data BOOTLOADER"; 48 | yModem = new YModem.Builder() 49 | .with(this) 50 | .filePath("你的文件夹路径") //存放到手机的文件路径 stroge/0/.../xx.bin 这种路径 51 | .fileName("你的文件名字") 52 | .checkMd5("") //Md5可以写可以不写 看自己的通讯协议 53 | .sendSize(1024) //可以修改成你需要的大小 54 | .callback(new YModemListener() { 55 | @Override 56 | public void onDataReady(byte[] data) { 57 | thread.write(data); 58 | } 59 | 60 | @Override 61 | public void onProgress(int currentSent, int total) { 62 | //进度条处理 63 | } 64 | 65 | @Override 66 | public void onSuccess() { 67 | //成功的显示 68 | } 69 | 70 | @Override 71 | public void onFailed(String reason) { 72 | 73 | } 74 | }).build(); 75 | yModem.start(customData); 76 | 77 | ``` 78 | 79 | old : 80 | 81 | ```java 82 | yModem = new YModem.Builder() 83 | .with(this) 84 | .filePath("你的文件夹路径") //存放到手机的文件路径 stroge/0/.../xx.bin 这种路径 85 | .fileName("你的文件名字") 86 | .checkMd5("") //Md5可以写可以不写 看自己的通讯协议 87 | .sendSize(1024) //可以修改成你需要的大小 88 | .callback(new YModemListener() { 89 | @Override 90 | public void onDataReady(byte[] data) { 91 | thread.write(data); 92 | } 93 | 94 | @Override 95 | public void onProgress(int currentSent, int total) { 96 | //进度条处理 97 | } 98 | 99 | @Override 100 | public void onSuccess() { 101 | //成功的显示 102 | } 103 | 104 | @Override 105 | public void onFailed(String reason) { 106 | 107 | } 108 | }).build(); 109 | yModem.start(); 110 | ``` 111 | 112 | 113 | 114 | ### Update May 16/2022 115 | 116 | Update compatible AndroidX 117 | 118 | ### Update Dec 1/2021 119 | 120 | update some bug 121 | 122 | ### Update 2020 8/11 123 | 124 | A simplified version of the Ymodem upgrade protocol has been added 125 | 126 | 127 | ### Support classic Bluetooth socket communication and ble 128 | 129 | 1. This update modified some wrong methods. 130 | 131 | 2. Increase the data size you can choose to send, as shown in the following code, you can modify the size and format of the data received by your device。 132 | 133 | 3. Supports Ble and classic Bluetooth. 134 | 135 | 4. CRC_Check16 136 | ```java 137 | 138 | public static String CRC16_Check(byte[] pushData, int length){ 139 | int Reg_CRC = 0xffff; 140 | int temp; 141 | int i,j; 142 | for(i=0;i> 1)^0xA001; 150 | }else{ 151 | Reg_CRC >>=1; 152 | } 153 | } 154 | } 155 | return Integer.toHexString((Reg_CRC&0xffff)); 156 | } 157 | 158 | 159 | /** 160 | * 计算CRC16校验码 161 | * 162 | * @param bytes 163 | * @return 164 | */ 165 | public static String getCRC(byte[] bytes) { 166 | int CRC = 0x0000ffff; 167 | int POLYNOMIAL = 0x0000a001; 168 | 169 | int i, j; 170 | for (i = 0; i < bytes.length; i++) { 171 | CRC ^= ((int) bytes[i] & 0x000000ff); 172 | for (j = 0; j < 8; j++) { 173 | if ((CRC & 0x00000001) != 0) { 174 | CRC >>= 1; 175 | CRC ^= POLYNOMIAL; 176 | } else { 177 | CRC >>= 1; 178 | } 179 | } 180 | } 181 | return Integer.toHexString(CRC); 182 | } 183 | 184 | 185 | 186 | ``` 187 | 188 | 189 | 190 | 191 | 192 | #### Gradle Use
193 | 194 | ```java 195 | allprojects { 196 | repositories { 197 | ... 198 | maven { url 'https://jitpack.io' } 199 | } 200 | } 201 | ``` 202 | 203 | 204 | ```java 205 | dependencies { 206 | implementation 'com.github.ArdWang:YModemLib:2.0.3' 207 | } 208 | 209 | ``` 210 | 211 | #### Maven Use
212 | 213 | ```java 214 | 215 | allprojects { 216 | repositories { 217 | ... 218 | maven { url 'https://jitpack.io' } 219 | } 220 | } 221 | 222 | 223 | 224 | jitpack.io 225 | https://jitpack.io 226 | 227 | 228 | ``` 229 | 230 | ```java 231 | 232 | com.github.ArdWang 233 | YModemLib 234 | version 235 | 236 | 237 | ``` 238 | 239 | For other operations, please see the operations in the app
240 | into [YModemBleDemo](https://github.com/ArdWang/YModemBleUpdate "悬停显示") 241 | 242 |

243 | YModem delegate 244 | ```java 245 | /** 246 | * ======================================================================================== 247 | * THE YMODEM: 248 | * Send 0x05>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>* 发送0x05 249 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C 250 | * SOH 00 FF "foo.c" "1064'' NUL[118] CRC CRC >>>>>>>>>>>>> 251 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 252 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C 253 | * STX 01 FE data[256] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>> 254 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 255 | * ACK STX 02 FD data[256] CRC CRC>>>>>>>>>>>>>>>>>>>>>>> 256 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 257 | * ACK STX 03 FC data[256] CRC CRC>>>>>>>>>>>>>>>>>>>>>>> 258 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 259 | * STX 04 FB data[256] CRC CRC>>>>>>>>>>>>>>>>>>>>>>> 260 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 261 | * SOH 05 FA data[100] 1A[28] CRC CRC>>>>>>>>>>>>>>>>>> 262 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 263 | * EOT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 264 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< NAK 265 | * EOT>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 266 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 267 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C 268 | * SOH 00 FF NUL[128] CRC CRC >>>>>>>>>>>>>>>>>>>>>>> 269 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 270 | * =========================================================================================== 271 | **/ 272 | 273 | ``` 274 | -------------------------------------------------------------------------------- /YModemLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /YModemLibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 27 11 | 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | 28 | implementation 'com.android.support:appcompat-v7:27.1.1' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 31 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 32 | } 33 | 34 | 35 | task deleteJar(type: Delete) { 36 | delete 'libs/jars/logmanagementlib.jar' 37 | } 38 | 39 | task createJar(type: Copy) { 40 | from('build/intermediates/aar_main_jar/release/') 41 | into('libs/jars/') 42 | include('classes.jar') 43 | rename('classes.jar', 'ymodem.jar') 44 | } 45 | 46 | createJar.dependsOn(deleteJar, build) 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /YModemLibrary/libs/jars/ymodem.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/YModemLibrary/libs/jars/ymodem.jar -------------------------------------------------------------------------------- /YModemLibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /YModemLibrary/src/androidTest/java/com/bw/yml/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.bw.yml.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/CRC16.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | /** 4 | * Uses table for irreducible polynomial: 1 + x^2 + x^15 + x^16 5 | */ 6 | 7 | public class CRC16 { 8 | 9 | private static final int[] table = { 10 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 11 | 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 12 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 13 | 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 14 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 15 | 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 16 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 17 | 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 18 | 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 19 | 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 20 | 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 21 | 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 22 | 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 23 | 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 24 | 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 25 | 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 26 | 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 27 | 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 28 | 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 29 | 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 30 | 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 31 | 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 32 | 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 33 | 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 34 | 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 35 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 36 | 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 37 | 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 38 | 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 39 | 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 40 | 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 41 | 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, 42 | }; 43 | 44 | public int getCRCLength() { 45 | return 2; 46 | } 47 | 48 | public long calcCRC(byte[] block) { 49 | int crc = 0x0000; 50 | for (byte b : block) { 51 | crc = ((crc << 8) ^ table[((crc >> 8) ^ (0xff & b))]) & 0xFFFF; 52 | } 53 | 54 | return crc; 55 | } 56 | 57 | public static int crc16_byte(int crc, byte b) { 58 | final int[] crc16_table = { 59 | 0x0000, 0xCC01, 0xD801, 0x1400, 60 | 0xF001, 0x3C00, 0x2800, 0xE401, 61 | 0xA001, 0x6C00, 0x7800, 0xB401, 62 | 0x5000, 0x9C01, 0x8801, 0x4400 63 | }; 64 | int temp; 65 | // Compute checksum of lower four bits of a byte. 66 | temp = crc16_table[crc & 0xF]; 67 | crc = (crc >> 4) & 0x0FFF; 68 | crc = crc ^ temp ^ crc16_table[b & 0xF]; 69 | // Now compute checksum of upper four bits of a byte. 70 | temp = crc16_table[crc & 0xF]; 71 | crc = (crc >> 4) & 0x0FFF; 72 | crc = crc ^ temp ^ crc16_table[(b >> 4) & 0xF]; 73 | return crc; 74 | } 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/FileStreamThread.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | /** 10 | * Thread for reading input Stream and encapsulating into a ymodem package 11 | * 12 | */ 13 | 14 | public class FileStreamThread extends Thread { 15 | 16 | private final Context mContext; 17 | private InputStream inputStream = null; 18 | private DataRaderListener listener; 19 | private final String filePath; 20 | private final AtomicBoolean isDataAcknowledged = new AtomicBoolean(false); 21 | private boolean isKeepRunning = false; 22 | private int fileByteSize = 0; 23 | 24 | FileStreamThread(Context mContext, String filePath, DataRaderListener listener) { 25 | this.mContext = mContext; 26 | this.filePath = filePath; 27 | this.listener = listener; 28 | } 29 | 30 | int getFileByteSize(){ 31 | if (fileByteSize == 0 || inputStream == null) { 32 | initStream(); 33 | } 34 | return fileByteSize; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | try { 40 | prepareData(); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | private void prepareData() throws IOException { 47 | initStream(); 48 | //1024 修改为 n 49 | byte[] block = new byte[YModem.mSize]; 50 | int dataLength; 51 | byte blockSequence = 1;//The data package of a file is actually started from 1 文件的数据包实际上是从1开始的。 52 | isDataAcknowledged.set(true); 53 | isKeepRunning = true; 54 | while (isKeepRunning) { 55 | 56 | if (!isDataAcknowledged.get()) { 57 | try { 58 | //We need to sleep for a while as the sending 1024 bytes data from ble would take several seconds 59 | //In my circumstances, this can be up to 3 seconds. 60 | ////我们需要睡眠一段时间,因为从BLE发送1024字节数据需要几秒钟。 61 | //在我的情况下,这可以长达3秒。 62 | Thread.sleep(100); 63 | } catch (InterruptedException e) { 64 | e.printStackTrace(); 65 | } 66 | continue; 67 | } 68 | 69 | if ((dataLength = inputStream.read(block)) == -1) { 70 | Lg.f("The file data has all been read..."); 71 | if (listener != null) { 72 | onStop(); 73 | listener.onFinish(); 74 | } 75 | break; 76 | } 77 | 78 | byte[] pack = YModemUtil.getDataPackage(block, dataLength, blockSequence); 79 | 80 | if (listener != null) { 81 | listener.onDataReady(pack); 82 | } 83 | 84 | blockSequence++; 85 | isDataAcknowledged.set(false); 86 | } 87 | 88 | } 89 | 90 | /** 91 | * When received response from the terminal ,we should keep the thread keep going 92 | */ 93 | void keepReading() { 94 | isDataAcknowledged.set(true); 95 | } 96 | 97 | void release() { 98 | onStop(); 99 | listener = null; 100 | } 101 | 102 | private void onStop() { 103 | isKeepRunning = false; 104 | isDataAcknowledged.set(false); 105 | fileByteSize = 0; 106 | onReadFinished(); 107 | } 108 | 109 | private void initStream() { 110 | if (inputStream == null) { 111 | try { 112 | inputStream = YModemUtil.getInputStream(mContext, filePath); 113 | fileByteSize = inputStream.available(); 114 | } catch (IOException e) { 115 | e.printStackTrace(); 116 | } 117 | } 118 | } 119 | 120 | private void onReadFinished() { 121 | if (inputStream != null) { 122 | try { 123 | inputStream.close(); 124 | inputStream = null; 125 | } catch (IOException e) { 126 | e.printStackTrace(); 127 | } 128 | } 129 | } 130 | 131 | public interface DataRaderListener { 132 | void onDataReady(byte[] data); 133 | void onFinish(); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/InputStreamSource.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import android.content.Context; 4 | import java.io.BufferedInputStream; 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | /** 10 | * Get InputStream from different source, files from sd card/assets supported. 11 | */ 12 | 13 | class InputStreamSource { 14 | //文件容量大小改为 32*n 15 | private static final int BUFFER_SIZE = 32 * YModem.mSize; 16 | //private static final String ERROR_UNSUPPORTED_SCHEME = "Unsupported file source"; 17 | 18 | InputStream getStream(Context context, String imageUri) throws IOException { 19 | switch (SourceScheme.ofUri(imageUri)) { 20 | case FILE: 21 | return getStreamFromFile(imageUri); 22 | 23 | case ASSETS: 24 | return getStreamFromAssets(context, imageUri); 25 | 26 | case UNKNOWN: 27 | default: 28 | return getStreamFromOtherSource(imageUri); 29 | } 30 | } 31 | 32 | private InputStream getStreamFromFile(String fileUri) throws IOException { 33 | String filePath = SourceScheme.FILE.crop(fileUri); 34 | return new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE); 35 | } 36 | 37 | private InputStream getStreamFromAssets(Context context, String fileUri) throws IOException { 38 | String filePath = SourceScheme.ASSETS.crop(fileUri); 39 | return context.getAssets().open(filePath); 40 | } 41 | 42 | /** 43 | * 从其它的地方获取数据 44 | */ 45 | private InputStream getStreamFromOtherSource(String fileUri) throws IOException{ 46 | return new BufferedInputStream(new FileInputStream(fileUri), BUFFER_SIZE); 47 | //throw new UnsupportedOperationException(String.format(ERROR_UNSUPPORTED_SCHEME, fileUri)); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/Lg.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import android.util.Log; 4 | 5 | public class Lg{ 6 | 7 | private static final String TAG = "YMODEM"; 8 | 9 | private Lg() { 10 | /* cannot be instantiated */ 11 | throw new UnsupportedOperationException("cannot be instantiated"); 12 | } 13 | 14 | private static final boolean DEBUGGING = true; 15 | 16 | public static void f(String msg) { 17 | if (DEBUGGING) { 18 | Log.e(TAG, msg); 19 | } 20 | } 21 | 22 | // 下面四个是默认tag的函数 23 | public static void i(String msg) { 24 | if (DEBUGGING) 25 | Log.i(TAG, msg); 26 | } 27 | 28 | public static void d(String msg) { 29 | if (DEBUGGING) 30 | Log.d(TAG, msg); 31 | } 32 | 33 | public static void e(String msg) { 34 | if (DEBUGGING) 35 | Lg.e(TAG, msg); 36 | } 37 | 38 | public static void v(String msg) { 39 | if (DEBUGGING) 40 | Log.v(TAG, msg); 41 | } 42 | 43 | public static void w(String msg) { 44 | if (DEBUGGING) 45 | Log.w(TAG, msg); 46 | } 47 | 48 | // 下面是传入自定义tag的函数 49 | public static void i(String tag, String msg) { 50 | if (DEBUGGING) 51 | Log.i(tag, msg); 52 | } 53 | 54 | public static void d(String tag, String msg) { 55 | if (DEBUGGING) 56 | Log.d(tag, msg); 57 | } 58 | 59 | public static void e(String tag, String msg) { 60 | if (DEBUGGING) 61 | Log.e(tag, msg); 62 | } 63 | 64 | public static void v(String tag, String msg) { 65 | if (DEBUGGING) 66 | Log.v(tag, msg); 67 | } 68 | 69 | public static void w(String tag, String msg) { 70 | if (DEBUGGING) 71 | Lg.w(tag, msg); 72 | } 73 | 74 | public static void e(String tag, String msg, Throwable throwable) { 75 | if (DEBUGGING) 76 | Log.e(tag, msg, throwable); 77 | } 78 | 79 | public static void wtf(String tag, String msg) { 80 | if (DEBUGGING) 81 | Log.wtf(tag, msg); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/SourceScheme.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import java.util.Locale; 4 | 5 | /** 6 | * create by ardWang 7 | */ 8 | public enum SourceScheme { 9 | 10 | FILE("file"), ASSETS("assets"), UNKNOWN(""); 11 | 12 | private final String scheme; 13 | private final String uriPrefix; 14 | 15 | SourceScheme(String scheme) { 16 | this.scheme = scheme; 17 | uriPrefix = scheme + "://"; 18 | } 19 | 20 | /** 21 | * Defines scheme of incoming URI 22 | * 23 | * @param uri URI for scheme detection 24 | * @return SourceScheme of incoming URI 25 | */ 26 | public static SourceScheme ofUri(String uri) { 27 | if (uri != null) { 28 | for (SourceScheme s : values()) { 29 | if (s.belongsTo(uri)) { 30 | return s; 31 | } 32 | } 33 | } 34 | return UNKNOWN; 35 | } 36 | 37 | private boolean belongsTo(String uri) { 38 | return uri.toLowerCase(Locale.US).startsWith(uriPrefix); 39 | } 40 | 41 | /** 42 | * Removed scheme part ("scheme://") from incoming URI 43 | */ 44 | public String crop(String uri) { 45 | if (!belongsTo(uri)) { 46 | throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme)); 47 | } 48 | return uri.substring(uriPrefix.length()); 49 | } 50 | } -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/TimeOutHelper.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import android.os.Handler; 4 | 5 | /** 6 | * A timer util for counting the time past after we sent a package to the terminal 7 | */ 8 | 9 | class TimeOutHelper { 10 | 11 | private ITimeOut listener; 12 | 13 | private final Handler timeoutHandler = new Handler(); 14 | 15 | private final Runnable timer = new Runnable() { 16 | @Override 17 | public void run() { 18 | stopTimer(); 19 | if (listener != null) { 20 | listener.onTimeOut(); 21 | } 22 | } 23 | }; 24 | 25 | void startTimer(ITimeOut timeoutListener, long delay) { 26 | listener = timeoutListener; 27 | timeoutHandler.postDelayed(timer, delay); 28 | } 29 | 30 | void stopTimer() { 31 | timeoutHandler.removeCallbacksAndMessages(null); 32 | } 33 | 34 | void unRegisterListener() { 35 | listener = null; 36 | } 37 | 38 | public interface ITimeOut { 39 | void onTimeOut(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/YModem.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import android.content.Context; 4 | import java.io.IOException; 5 | 6 | /** 7 | * ======================================================================================== 8 | * THE YMODEM: 9 | * Send 0x05>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>* 发送0x05 10 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C 11 | * SOH 00 FF "foo.c" "1064'' NUL[118] CRC CRC >>>>>>>>>>>>> 12 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 13 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C 14 | * STX 01 FE data[n] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>> 15 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 16 | * ACK STX 02 FD data[n] CRC CRC>>>>>>>>>>>>>>>>>>>>>>> 17 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 18 | * ACK STX 03 FC data[n] CRC CRC>>>>>>>>>>>>>>>>>>>>>>> 19 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 20 | * STX 04 FB data[n] CRC CRC>>>>>>>>>>>>>>>>>>>>>>> 21 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 22 | * SOH 05 FA data[100] 1A[28] CRC CRC>>>>>>>>>>>>>>>>>> 23 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 24 | * EOT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 25 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< NAK 26 | * EOT>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 27 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 28 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C 29 | * SOH 00 FF NUL[128] CRC CRC >>>>>>>>>>>>>>>>>>>>>>> 30 | * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK 31 | * =========================================================================================== 32 | * 33 | * 传输协议 编辑 ArdWang 34 | * 于 2018/6/5 15:49完成 35 | * 36 | * version v2.0.0 37 | * version v2.0.1 38 | * 39 | * version v2.0.2 40 | * 41 | */ 42 | 43 | public class YModem implements FileStreamThread.DataRaderListener { 44 | 45 | private static final int STEP_HELLO = 0x00; 46 | private static final int STEP_FILE_NAME = 0x01; 47 | private static final int STEP_FILE_BODY = 0x02; 48 | private static final int STEP_EOT = 0x03; 49 | private static final int STEP_END = 0x04; 50 | private static int CURR_STEP = STEP_HELLO; 51 | 52 | private static final byte ACK = 0x06; /* ACKnowlege */ 53 | private static final byte NAK = 0x15; /* Negative AcKnowlege */ 54 | private static final byte CAN = 0x18; /* CANcel character */ 55 | private static final byte ST_C = 'C'; 56 | private static final String MD5_OK = "MD5_OK"; 57 | private static final String MD5_ERR = "MD5_ERR"; 58 | 59 | private final Context mContext; 60 | private final String filePath; 61 | private final String fileNameString; 62 | private final String fileMd5String; 63 | private final YModemListener listener; 64 | 65 | private final TimeOutHelper timerHelper = new TimeOutHelper(); 66 | private FileStreamThread streamThread; 67 | 68 | //bytes has been sent of this transmission 69 | private int bytesSent = 0; 70 | //package data of current sending, used for int case of fail 71 | private byte[] currSending = null; 72 | private int packageErrorTimes = 0; 73 | private static final int MAX_PACKAGE_SEND_ERROR_TIMES = 6; 74 | //the timeout interval for a single package 75 | private static final int PACKAGE_TIME_OUT = 6000; 76 | static Integer mSize = 1024; 77 | 78 | /** 79 | * Construct of the YModemBLE,you may don't need the fileMD5 checking,remove it 80 | * YMODESMLE的构造,您可能不需要FLIMD5检查,删除它 81 | * 82 | * @param filePath absolute path of the file 83 | * @param fileNameString file name for sending to the terminal 84 | * @param fileMd5String md5 for terminal checking after transmission finished 传输结束后的终端检查MD5 85 | */ 86 | private YModem(Context context, String filePath, 87 | String fileNameString, String fileMd5String,Integer size, 88 | YModemListener listener) { 89 | this.filePath = filePath; 90 | this.fileNameString = fileNameString; 91 | this.fileMd5String = fileMd5String; 92 | if(size==0) { 93 | size = 1024; 94 | } 95 | mSize = size; 96 | this.mContext = context; 97 | this.listener = listener; 98 | } 99 | 100 | /** 101 | * Start the transmission 102 | */ 103 | public void start(String data) { 104 | sendData(data); 105 | } 106 | 107 | /** 108 | * Stop the transmission when you don't need it or shut it down in an accident 109 | * 停止传输当你不需要它或关闭它在一次事故 110 | */ 111 | public void stop() { 112 | bytesSent = 0; 113 | currSending = null; 114 | packageErrorTimes = 0; 115 | if (streamThread != null) { 116 | streamThread.release(); 117 | } 118 | timerHelper.stopTimer(); 119 | timerHelper.unRegisterListener(); 120 | } 121 | 122 | /** 123 | * Method for the outer caller when received data from the terminal 124 | * 接收来自终端的数据时外部呼叫者的方法 125 | */ 126 | public void onReceiveData(byte[] respData) { 127 | //Stop the package timer 128 | timerHelper.stopTimer(); 129 | if (respData != null && respData.length > 0) { 130 | Lg.f("YModem received " + respData.length + " bytes."); 131 | switch (CURR_STEP) { 132 | case STEP_HELLO: 133 | handleData(respData); 134 | break; 135 | case STEP_FILE_NAME: 136 | handleFileName(respData); 137 | break; 138 | case STEP_FILE_BODY: 139 | handleFileBody(respData); 140 | break; 141 | case STEP_EOT: 142 | handleEOT(respData); 143 | break; 144 | case STEP_END: 145 | handleEnd(respData); 146 | break; 147 | default: 148 | break; 149 | } 150 | } else { 151 | Lg.f("The terminal do responsed something, but received nothing??"); 152 | } 153 | } 154 | 155 | /** 156 | * ============================================================================== 157 | * Methods for sending data begin 158 | * 159 | * 此方法更改如果没有第一包标注位就不需要发送数据 160 | * =》直接发送FileName 161 | * 162 | * ============================================================================== 163 | * 164 | */ 165 | private void sendData(String data) { 166 | streamThread = new FileStreamThread(mContext, filePath, this); 167 | if(data != null) { 168 | CURR_STEP = STEP_HELLO; 169 | Lg.f("StartData!!!"); 170 | byte[] hello = YModemUtil.getYModelData(data); 171 | sendPackageData(hello); 172 | }else{ 173 | packageErrorTimes = 0; 174 | sendFileName(); 175 | } 176 | } 177 | 178 | private void sendFileName() { 179 | CURR_STEP = STEP_FILE_NAME; 180 | Lg.f("sendFileName"); 181 | try { 182 | int fileByteSize = streamThread.getFileByteSize(); 183 | byte[] fileNamePackage = YModemUtil.getFileNamePackage(fileNameString, fileByteSize 184 | , fileMd5String); 185 | sendPackageData(fileNamePackage); 186 | } catch (IOException e) { 187 | e.printStackTrace(); 188 | } 189 | } 190 | 191 | private void startSendFileData() { 192 | CURR_STEP = STEP_FILE_BODY; 193 | Lg.f("startSendFileData"); 194 | streamThread.start(); 195 | } 196 | 197 | //Callback from the data reading thread when a data package is ready 198 | @Override 199 | public void onDataReady(byte[] data) { 200 | sendPackageData(data); 201 | } 202 | 203 | private void sendEOT() { 204 | CURR_STEP = STEP_EOT; 205 | Lg.f("sendEOT"); 206 | if (listener != null) { 207 | listener.onDataReady(YModemUtil.getEOT()); 208 | } 209 | } 210 | 211 | private void sendEND() { 212 | CURR_STEP = STEP_END; 213 | Lg.f("sendEND"); 214 | if (listener != null) { 215 | try { 216 | listener.onDataReady(YModemUtil.getEnd()); 217 | } catch (IOException e) { 218 | e.printStackTrace(); 219 | } 220 | } 221 | } 222 | 223 | private void sendPackageData(byte[] packageData) { 224 | if (listener != null && packageData != null) { 225 | currSending = packageData; 226 | //Start the timer, it will be cancelled when reponse received, 227 | // or trigger the timeout and resend the current package data 228 | //启动计时器,当收到回复时将被取消, 229 | //或触发超时并重新发送当前包数据 230 | timerHelper.startTimer(timeoutListener, PACKAGE_TIME_OUT); 231 | listener.onDataReady(packageData); 232 | } 233 | } 234 | 235 | /** 236 | * ============================================================================== 237 | * Method for handling the response of a package 238 | * ============================================================================== 239 | */ 240 | private void handleData(byte[] value) { 241 | int character = value[0]; 242 | if (character == ST_C) {//Receive "C" for "HELLO" 243 | Lg.f("Received 'C'"); 244 | packageErrorTimes = 0; 245 | sendFileName(); 246 | } else { 247 | handleOthers(character); 248 | } 249 | } 250 | 251 | //The file name package was responsed 252 | private void handleFileName(byte[] value) { 253 | if (value.length == 2 && value[0] == ACK && value[1] == ST_C) {//Receive 'ACK C' for file name 254 | Lg.f("Received 'ACK C'"); 255 | packageErrorTimes = 0; 256 | startSendFileData(); 257 | } else if (value[0] == ST_C) {//Receive 'C' for file name, this package should be resent 258 | Lg.f("Received 'C'"); 259 | handlePackageFail("Received 'C' without 'ACK' after sent file name"); 260 | } else { 261 | handleOthers(value[0]); 262 | } 263 | } 264 | 265 | private void handleFileBody(byte[] value) { 266 | if (value.length == 1 && value[0] == ACK) {//Receive ACK for file data 267 | Lg.f("Received 'ACK'"); 268 | packageErrorTimes = 0; 269 | bytesSent += currSending.length; 270 | try { 271 | if (listener != null) { 272 | listener.onProgress(bytesSent, streamThread.getFileByteSize()); 273 | } 274 | } catch (Exception e) { 275 | e.printStackTrace(); 276 | } 277 | streamThread.keepReading(); 278 | 279 | } else if (value.length == 1 && value[0] == ST_C) { 280 | Lg.f("Received 'C'"); 281 | //Receive C for file data, the ymodem cannot handle this circumstance, transmission failed... 282 | handlePackageFail("Received 'C' after sent file data"); 283 | } else { 284 | handleOthers(value[0]); 285 | } 286 | } 287 | 288 | private void handleEOT(byte[] value) { 289 | if (value[0] == ACK) { 290 | Lg.f("Received 'ACK'"); 291 | packageErrorTimes = 0; 292 | sendEND(); 293 | } else if (value[0] == ST_C) {//As we haven't received ACK, we should resend EOT 294 | handlePackageFail("Received 'C' after sent EOT"); 295 | } else if(value[0]==NAK){ //如果是NAK的话 再次发送一次EOT数据 296 | sendEOT(); 297 | }else{ 298 | handleOthers(value[0]); 299 | } 300 | } 301 | 302 | private void handleEnd(byte[] character) { 303 | if (character[0] == ACK) {//The last ACK represents that the transmission has been finished, but we should validate the file 304 | Lg.f("Received 'ACK'"); 305 | packageErrorTimes = 0; 306 | //发送已经成功,完全结束 307 | if (listener != null) { 308 | listener.onSuccess(); 309 | } 310 | } else if ((new String(character)).equals(MD5_OK)) {//The file data has been checked,Well Done! 311 | Lg.f("Received 'MD5_OK'"); 312 | stop(); 313 | if (listener != null) { 314 | listener.onSuccess(); 315 | } 316 | } else if ((new String(character)).equals(MD5_ERR)) {//Oops...Transmission Failed... 317 | Lg.f("Received 'MD5_ERR'"); 318 | stop(); 319 | if (listener != null) { 320 | listener.onFailed("MD5 check failed!!!"); 321 | } 322 | } else { 323 | handleOthers(character[0]); 324 | } 325 | } 326 | 327 | private void handleOthers(int character) { 328 | if (character == NAK) {//We need to resend this package as the terminal failed when checking the crc 329 | Lg.f("Received 'NAK'"); 330 | handlePackageFail("Received NAK"); 331 | } else if (character == CAN) {//Some big problem occurred, transmission failed... 332 | Lg.f("Received 'CAN'"); 333 | if (listener != null) { 334 | listener.onFailed("Received CAN"); 335 | } 336 | stop(); 337 | } 338 | } 339 | 340 | //Handle a failed package data ,resend it up to MAX_PACKAGE_SEND_ERROR_TIMES times. 341 | //处理失败的包数据 342 | //If still failed, then the transmission failed. 343 | private void handlePackageFail(String reason) { 344 | packageErrorTimes++; 345 | Lg.f("Fail:" + reason + " for " + packageErrorTimes + " times"); 346 | if (packageErrorTimes < MAX_PACKAGE_SEND_ERROR_TIMES) { 347 | sendPackageData(currSending); 348 | } else { 349 | //Still, we stop the transmission, release the resources 350 | stop(); 351 | if (listener != null) { 352 | listener.onFailed(reason); 353 | } 354 | } 355 | } 356 | 357 | /* The InputStream data reading thread was done */ 358 | @Override 359 | public void onFinish() { 360 | sendEOT(); 361 | } 362 | 363 | //The timeout listener 364 | private final TimeOutHelper.ITimeOut timeoutListener = new TimeOutHelper.ITimeOut() { 365 | @Override 366 | public void onTimeOut() { 367 | Lg.f("------ time out ------"); 368 | if (currSending != null) { 369 | handlePackageFail("package timeout..."); 370 | } 371 | } 372 | }; 373 | 374 | public static class Builder { 375 | private Context context; 376 | private String filePath; 377 | private String fileNameString; 378 | private String fileMd5String; 379 | private Integer size; 380 | private YModemListener listener; 381 | 382 | public Builder with(Context context) { 383 | this.context = context; 384 | return this; 385 | } 386 | 387 | public Builder filePath(String filePath) { 388 | this.filePath = filePath; 389 | return this; 390 | } 391 | 392 | public Builder fileName(String fileName) { 393 | this.fileNameString = fileName; 394 | return this; 395 | } 396 | 397 | public Builder sendSize(Integer size){ 398 | this.size = size; 399 | return this; 400 | } 401 | 402 | public Builder checkMd5(String fileMd5String) { 403 | this.fileMd5String = fileMd5String; 404 | return this; 405 | } 406 | 407 | public Builder callback(YModemListener listener) { 408 | this.listener = listener; 409 | return this; 410 | } 411 | 412 | public YModem build() { 413 | return new YModem(context, filePath, fileNameString, fileMd5String, size, listener); 414 | } 415 | 416 | } 417 | 418 | } 419 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/YModemListener.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | /** 4 | * Listener of the transmission process 5 | */ 6 | public interface YModemListener { 7 | 8 | /* the data package has been encapsulated */ 9 | void onDataReady(byte[] data); 10 | 11 | /*just the file data progress*/ 12 | void onProgress(int currentSent, int total); 13 | 14 | /* the file has been correctly sent to the terminal */ 15 | void onSuccess(); 16 | 17 | /* the task has failed with several remedial measures like retrying some times*/ 18 | void onFailed(String reason); 19 | 20 | } -------------------------------------------------------------------------------- /YModemLibrary/src/main/java/com/bw/yml/YModemUtil.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import android.content.Context; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.DataOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Arrays; 9 | 10 | /** 11 | * Util for encapsulating data package of ymodem protocol 12 | *

13 | * Created by leonxtp on 2017/9/16. 14 | * Modified by rnd on 2019/10/28 15 | */ 16 | 17 | class YModemUtil { 18 | 19 | /*This is my concrete ymodem start signal, customise it to your needs*/ 20 | //private static final String Data = "Data BOOTLOADER"; 21 | private static final byte SOH = 0x01; /* Start Of Header with data size :128*/ 22 | private static final byte STX = 0x02; /* Start Of Header with data size : 1024*/ 23 | private static final byte EOT = 0x04; /* End Of Transmission */ 24 | private static final byte CPMEOF = 0x1A;/* Fill the last package if not long enough */ 25 | private static final CRC16 crc16 = new CRC16(); 26 | //private static byte[] mInitBytes; 27 | 28 | /* 29 | * Get the first package data for hello with a terminal 30 | */ 31 | // static byte[] getYModelData() { 32 | // return Data.getBytes(); 33 | // } 34 | 35 | /** 36 | * Get the first package data for hello with a terminal 37 | * 2024 38 | * 3/13更新 修改成动态的 调用采用 39 | * String customData = "Customized Data"; 40 | * byte[] dataBytes = getYModelData(customData); 41 | */ 42 | static byte[] getYModelData(String dynamicData) { 43 | return dynamicData.getBytes(); 44 | } 45 | 46 | 47 | /** 48 | * Get the file name package data 49 | * 50 | * @param fileNameString file name in String 51 | * @param fileByteSize file byte size of int value 52 | * @param fileMd5String the md5 of the file in String 53 | * 54 | */ 55 | static byte[] getFileNamePackage(String fileNameString, 56 | int fileByteSize, 57 | String fileMd5String) throws IOException { 58 | 59 | byte seperator = 0x0; 60 | String fileSize = fileByteSize + ""; 61 | byte[] byteFileSize = fileSize.getBytes(); 62 | 63 | byte[] fileNameBytes1 = concat(fileNameString.getBytes(), 64 | new byte[]{seperator}, 65 | byteFileSize); 66 | 67 | byte[] fileNameBytes2; 68 | fileNameBytes2 = Arrays.copyOf(concat(fileNameBytes1, 69 | new byte[]{seperator}, 70 | fileMd5String.getBytes()), 128); 71 | 72 | byte seq = 0x00; 73 | return getDataPackage(fileNameBytes2, 128, seq); 74 | } 75 | 76 | /** 77 | * Get a encapsulated package data block 78 | * 79 | * @param block byte data array 80 | * @param dataLength the actual content length in the block without 0 filled in it. 81 | * @param sequence the package serial number 82 | * @return a encapsulated package data block 83 | */ 84 | static byte[] getDataPackage(byte[] block, int dataLength, byte sequence) throws IOException { 85 | // 选择合适的包头类型:SOH (128字节) 或 STX (1024字节) 86 | byte headerType = (block.length == 128) ? SOH : (block.length == 1024) ? STX : SOH; 87 | byte[] header = getDataHeader(sequence, headerType); 88 | 89 | // 填充剩余数据为 CPMEOF(如果数据不足) 90 | if (dataLength < block.length) { 91 | int startFil = dataLength; 92 | while (startFil < block.length) { 93 | block[startFil] = CPMEOF; 94 | startFil++; 95 | } 96 | } 97 | 98 | // 计算CRC校验 99 | short crc = (short) crc16.calcCRC(block); 100 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 101 | DataOutputStream dos = new DataOutputStream(baos); 102 | dos.writeShort(crc); 103 | dos.close(); 104 | byte[] crcBytes = baos.toByteArray(); 105 | 106 | // 返回完整的包(包头 + 数据 + CRC) 107 | return concat(header, block, crcBytes); 108 | } 109 | 110 | 111 | /** 112 | * Get the EOT package 113 | */ 114 | static byte[] getEOT() { 115 | return new byte[]{EOT}; 116 | } 117 | 118 | /** 119 | * Get the Last package 120 | */ 121 | static byte[] getEnd() throws IOException { 122 | byte seq = 0x00; 123 | return getDataPackage(new byte[128], 128, seq); 124 | } 125 | 126 | /** 127 | * Get InputStream from Assets, you can customize it from the other sources 128 | * 129 | * @param fileAbsolutePath absolute path of the file in asstes 130 | */ 131 | static InputStream getInputStream(Context context, String fileAbsolutePath) throws IOException { 132 | return new InputStreamSource().getStream(context, fileAbsolutePath); 133 | } 134 | 135 | private static byte[] getDataHeader(byte sequence, byte start) { 136 | //The serial number of the package increases Cyclically up to 256 137 | byte modSequence = (byte) (sequence % 0x256); 138 | byte complementSeq = (byte) ~modSequence; 139 | 140 | return concat(new byte[]{start}, 141 | new byte[]{modSequence}, 142 | new byte[]{complementSeq}); 143 | } 144 | 145 | private static byte[] concat(byte[] a, byte[] b, byte[] c) { 146 | int aLen = a.length; 147 | int bLen = b.length; 148 | int cLen = c.length; 149 | byte[] concated = new byte[aLen + bLen + cLen]; 150 | System.arraycopy(a, 0, concated, 0, aLen); 151 | System.arraycopy(b, 0, concated, aLen, bLen); 152 | System.arraycopy(c, 0, concated, aLen + bLen, cLen); 153 | return concated; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /YModemLibrary/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | YModemLibrary 3 | 4 | -------------------------------------------------------------------------------- /YModemLibrary/src/test/java/com/bw/yml/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bw.yml; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 33 5 | defaultConfig { 6 | applicationId "com.bw.ym" 7 | minSdkVersion 19 8 | targetSdkVersion 33 9 | versionCode 2 10 | versionName "1.0.3" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | compileOptions { 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'androidx.appcompat:appcompat:1.3.0' 28 | implementation 'com.android.support.constraint:constraint-layout:2.0.4' 29 | testImplementation 'junit:junit:4.13.2' 30 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 31 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 32 | 33 | api project(':YModemLibrary') 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/bw/ym/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.bw.ym; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.bw.ym", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/bw/ym/demo/ConnectThread.java: -------------------------------------------------------------------------------- 1 | package com.bw.ym.demo; 2 | 3 | import android.bluetooth.BluetoothSocket; 4 | import android.util.Log; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.util.Arrays; 10 | 11 | 12 | /* 13 | * Copyright © 2018 Radiance Instruments Ltd. All rights reserved. 14 | * author ArdWang 15 | * email 278161009@qq.com 16 | * Created by ArdWang on 10/28/19. 17 | */ 18 | 19 | public class ConnectThread extends Thread{ 20 | private static final String TAG = "ConnectedThread"; 21 | private BluetoothSocket mmSocket; 22 | private InputStream mmInStream; 23 | private OutputStream mmOutStream; 24 | //是否是主动断开 25 | private boolean isStop = false; 26 | //发起蓝牙连接的线程 27 | private ConnectThread connectThread; 28 | 29 | public void terminalClose(ConnectThread connectThread){ 30 | isStop = true; 31 | this.connectThread = connectThread; 32 | } 33 | 34 | public ConnectThread(BluetoothSocket socket){ 35 | mmSocket = socket; 36 | 37 | InputStream tmpIn = null; 38 | OutputStream tmpOut = null; 39 | 40 | //使用临时对象获取输入和输出流,因为成员流是静态类型 41 | 42 | //1、获取 InputStream 和 OutputStream 43 | try { 44 | tmpIn = socket.getInputStream(); 45 | tmpOut = socket.getOutputStream(); 46 | 47 | } catch (IOException e) { 48 | Log.e(TAG,"ConnectedThread-->获取InputStream 和 OutputStream异常!"); 49 | } 50 | 51 | mmInStream = tmpIn; 52 | mmOutStream = tmpOut; 53 | 54 | if(mmInStream != null){ 55 | Log.d(TAG,"ConnectedThread-->已获取InputStream"); 56 | } 57 | 58 | if(mmOutStream != null){ 59 | Log.d(TAG,"ConnectedThread-->已获取OutputStream"); 60 | } 61 | 62 | } 63 | 64 | public void run(){ 65 | //最大缓存区 存放流 66 | byte[] buffer = new byte[1024 * 2]; //buffer store for the stream 67 | //从流的read()方法中读取的字节数 68 | int bytes = 0; //bytes returned from read() 69 | //持续监听输入流直到发生异常 70 | while(!isStop){ 71 | try { 72 | if(mmInStream == null){ 73 | Log.e(TAG,"ConnectedThread:run-->输入流mmInStream == null"); 74 | break; 75 | } 76 | //先判断是否有数据,有数据再读取 77 | if(mmInStream.available() != 0){ 78 | //2、接收数据 79 | bytes = mmInStream.read(buffer); //从(mmInStream)输入流中(读取内容)读取的一定数量字节数,并将它们存储到缓冲区buffer数组中,bytes为实际读取的字节数 80 | byte[] b = Arrays.copyOf(buffer,bytes); //存放实际读取的数据内容 81 | Log.w(TAG,"ConnectedThread:run-->收到消息,长度" + b.length + "->" + bytes2HexString(b, b.length)); //有空格的16进制字符串 82 | if(onSendReceiveDataListener != null){ 83 | onSendReceiveDataListener.onReceiveDataSuccess(b); //成功收到消息 84 | } 85 | } 86 | 87 | } catch (IOException e) { 88 | Log.e(TAG,"ConnectedThread:run-->接收消息异常!" + e.getMessage()); 89 | if(onSendReceiveDataListener != null){ 90 | onSendReceiveDataListener.onReceiveDataError("接收消息异常:" + e.getMessage()); //接收消息异常 91 | } 92 | //关闭流和socket 93 | boolean isClose = cancel(); 94 | if(isClose){ 95 | Log.e(TAG,"ConnectedThread:run-->接收消息异常,成功断开连接!"); 96 | } 97 | break; 98 | } 99 | } 100 | //关闭流和socket 101 | boolean isClose = cancel(); 102 | if(isClose){ 103 | Log.d(TAG,"ConnectedThread:run-->接收消息结束,断开连接!"); 104 | } 105 | } 106 | 107 | //发送数据 108 | public boolean write(byte[] bytes){ 109 | try { 110 | if(mmOutStream == null){ 111 | Log.e(TAG, "mmOutStream == null"); 112 | return false; 113 | } 114 | 115 | //发送数据 116 | mmOutStream.write(bytes); 117 | Log.d(TAG, "写入成功:"+ bytes2HexString(bytes, bytes.length)); 118 | if(onSendReceiveDataListener != null){ 119 | onSendReceiveDataListener.onSendDataSuccess(bytes); //发送数据成功回调 120 | } 121 | return true; 122 | 123 | } catch (IOException e) { 124 | Log.e(TAG, "写入失败:"+ bytes2HexString(bytes, bytes.length)); 125 | if(onSendReceiveDataListener != null){ 126 | onSendReceiveDataListener.onSendDataError(bytes,"写入失败"); //发送数据失败回调 127 | } 128 | return false; 129 | } 130 | } 131 | 132 | /** 133 | * 释放 134 | * @return true 断开成功 false 断开失败 135 | */ 136 | public boolean cancel(){ 137 | try { 138 | if(mmInStream != null){ 139 | mmInStream.close(); //关闭输入流 140 | } 141 | if(mmOutStream != null){ 142 | mmOutStream.close(); //关闭输出流 143 | } 144 | if(mmSocket != null){ 145 | mmSocket.close(); //关闭socket 146 | } 147 | if(connectThread != null){ 148 | connectThread.cancel(); 149 | } 150 | 151 | connectThread = null; 152 | mmInStream = null; 153 | mmOutStream = null; 154 | mmSocket = null; 155 | 156 | Log.w(TAG,"ConnectedThread:cancel-->成功断开连接"); 157 | return true; 158 | 159 | } catch (IOException e) { 160 | // 任何一部分报错,都将强制关闭socket连接 161 | mmInStream = null; 162 | mmOutStream = null; 163 | mmSocket = null; 164 | 165 | Log.e(TAG, "ConnectedThread:cancel-->断开连接异常!" + e.getMessage()); 166 | return false; 167 | } 168 | } 169 | 170 | /** 171 | * 字节数组-->16进制字符串 172 | * @param b 字节数组 173 | * @param length 字节数组长度 174 | * @return 16进制字符串 有空格类似“0A D5 CD 8F BD E5 F8” 175 | */ 176 | public static String bytes2HexString(byte[] b, int length) { 177 | StringBuffer result = new StringBuffer(); 178 | String hex; 179 | for (int i = 0; i < length; i++) { 180 | hex = Integer.toHexString(b[i] & 0xFF); 181 | if (hex.length() == 1) { 182 | hex = '0' + hex; 183 | } 184 | result.append(hex.toUpperCase()).append(" "); 185 | } 186 | return result.toString(); 187 | } 188 | 189 | private OnSendReceiveDataListener onSendReceiveDataListener; 190 | 191 | public void setOnSendReceiveDataListener(OnSendReceiveDataListener onSendReceiveDataListener) { 192 | this.onSendReceiveDataListener = onSendReceiveDataListener; 193 | } 194 | 195 | //收发数据监听者 196 | public interface OnSendReceiveDataListener{ 197 | void onSendDataSuccess(byte[] data); //发送数据结束 198 | void onSendDataError(byte[] data, String errorMsg); //发送数据出错 199 | void onReceiveDataSuccess(byte[] buffer); //接收到数据 200 | void onReceiveDataError(String errorMsg); //接收数据出错 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /app/src/main/java/com/bw/ym/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.bw.ym.demo; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothSocket; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | import androidx.appcompat.app.AppCompatActivity; 11 | 12 | import com.bw.ym.R; 13 | import com.bw.yml.YModem; 14 | import com.bw.yml.YModemListener; 15 | 16 | /** 17 | * 使用介绍 18 | * 版本 v2.0.0-> 19 | * 如果你想使用请你配合你的蓝牙一起使用 20 | * 具体操操作 有相对应的Demo 21 | * 这里只是一个实列 这是代表经典蓝牙 22 | * 本demo只能实现怎么发送数据的过程 具体经典蓝牙连接socket需要去查看文档 23 | */ 24 | 25 | public class MainActivity extends AppCompatActivity implements ConnectThread.OnSendReceiveDataListener { 26 | 27 | private YModem yModem; 28 | 29 | private ConnectThread thread; 30 | 31 | private BluetoothAdapter bluetoothAdapter; 32 | 33 | private final static String TAG = "MainActivity"; 34 | 35 | private BluetoothSocket bluetoothSocket; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_main); 41 | initData(); 42 | } 43 | 44 | private void initData(){ 45 | initBluetooth(); 46 | if(bluetoothSocket!=null) 47 | thread = new ConnectThread(bluetoothSocket); 48 | } 49 | 50 | private void initBluetooth() { 51 | bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 52 | if(bluetoothAdapter == null){ 53 | Toast.makeText(this, "当前手机设备不支持蓝牙", Toast.LENGTH_SHORT).show(); 54 | }else{ 55 | //手机设备支持蓝牙,判断蓝牙是否已开启 56 | if(bluetoothAdapter.isEnabled()){ 57 | Toast.makeText(this, "手机蓝牙已开启", Toast.LENGTH_SHORT).show(); 58 | }else{ 59 | //蓝牙没有打开,去打开蓝牙。推荐使用第二种打开蓝牙方式 60 | //第一种方式:直接打开手机蓝牙,没有任何提示 61 | // bluetoothAdapter.enable(); //BLUETOOTH_ADMIN权限 62 | //第二种方式:友好提示用户打开蓝牙 63 | Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 64 | startActivity(enableBtIntent); 65 | } 66 | } 67 | } 68 | 69 | private void searchBtDevice() { 70 | if(bluetoothAdapter.isDiscovering()){ //当前正在搜索设备... 71 | return; 72 | } 73 | //开始搜索 74 | bluetoothAdapter.startDiscovery(); 75 | } 76 | 77 | 78 | 79 | /** 80 | * 开始Ymodel放在蓝牙点击按钮 启动的时候使用 81 | */ 82 | private void startYmodem(){ 83 | String customData = "Customized Data"; 84 | yModem = new YModem.Builder() 85 | .with(this) 86 | .filePath("你的文件夹路径") //存放到手机的文件路径 stroge/0/.../xx.bin 这种路径 87 | .fileName("你的文件名字") 88 | .checkMd5("") //Md5可以写可以不写 看自己的通讯协议 89 | .sendSize(1024) //可以修改成你需要的大小 90 | .callback(new YModemListener() { 91 | @Override 92 | public void onDataReady(byte[] data) { 93 | thread.write(data); 94 | } 95 | 96 | @Override 97 | public void onProgress(int currentSent, int total) { 98 | //进度条处理 99 | } 100 | 101 | @Override 102 | public void onSuccess() { 103 | //成功的显示 104 | } 105 | 106 | @Override 107 | public void onFailed(String reason) { 108 | 109 | } 110 | }).build(); 111 | 112 | // 默认为空 113 | yModem.start(null); 114 | // 有需要的时候再添加 115 | //yModem.start(customData); 116 | } 117 | 118 | //用于接受到你蓝牙设备给你反馈的蓝牙信息 119 | public void onDataReceivedFromBLE(byte[] data) { 120 | yModem.onReceiveData(data); 121 | } 122 | 123 | /*stop the transmission*/ 124 | public void onStopClick(View view) { 125 | yModem.stop(); 126 | } 127 | 128 | 129 | @Override 130 | protected void onDestroy() { 131 | super.onDestroy(); 132 | yModem.stop(); 133 | 134 | //你相应的一系列蓝牙的操作也要停止掉 135 | } 136 | 137 | 138 | @Override 139 | public void onSendDataSuccess(byte[] data) { 140 | //成功数据的打印 141 | } 142 | 143 | @Override 144 | public void onSendDataError(byte[] data, String errorMsg) { 145 | 146 | } 147 | 148 | @Override 149 | public void onReceiveDataSuccess(byte[] data) { 150 | if(data.length>0) 151 | yModem.onDataReady(data); 152 | } 153 | 154 | @Override 155 | public void onReceiveDataError(String errorMsg) { 156 | 157 | } 158 | 159 | 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BWYModem 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/bw/ym/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bw.ym; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:4.1.0' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | android.useAndroidX=true 16 | android.enableJetifier=true 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdWang/YModemlib_Android/e1196c8e0a2e7817293960af619040e3e7846c1b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 06 08:54:03 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':YModemLibrary' 2 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | wjprogram@gmail.com 7 | 123456 8 | central 9 | 10 | 11 | wjprogram@gmail.com 12 | 123456 13 | snapshots 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | false 22 | 23 | central 24 | github-libs-release 25 | https://programs.jfrog.io/artifactory/github-libs-release 26 | 27 | 28 | 29 | snapshots 30 | github-libs-snapshot 31 | https://programs.jfrog.io/artifactory/github-libs-snapshot 32 | 33 | 34 | 35 | 36 | 37 | false 38 | 39 | central 40 | github-libs-release 41 | https://programs.jfrog.io/artifactory/github-libs-release 42 | 43 | 44 | 45 | snapshots 46 | github-libs-snapshot 47 | https://programs.jfrog.io/artifactory/github-libs-snapshot 48 | 49 | 50 | artifactory 51 | 52 | 53 | 54 | artifactory 55 | 56 | 57 | 58 | central 59 | a0p7vf5eepvwc-artifactory-primary-0-releases 60 | https://programs.jfrog.io/artifactory/github-libs-release 61 | 62 | 63 | 64 | 65 | --------------------------------------------------------------------------------