├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── leonxtp │ │ └── ymodemble │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── leonxtp │ │ │ └── ymodemble │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.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 │ └── leonxtp │ └── ymodemble │ └── ExampleUnitTest.java ├── build.gradle ├── findbugs-android-exclude.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── leonxtp │ │ └── library │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── leonxtp │ │ │ └── library │ │ │ ├── CRC16.java │ │ │ ├── FileStreamThread.java │ │ │ ├── InputStreamSource.java │ │ │ ├── L.java │ │ │ ├── SourceScheme.java │ │ │ ├── TimeOutHelper.java │ │ │ ├── YModem.java │ │ │ ├── YModemListener.java │ │ │ └── YModemUtil.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── leonxtp │ └── library │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | 9 | # built application files 10 | *.apk 11 | 12 | # files for the dex VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # generated files 19 | bin/ 20 | gen/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Eclipse project files 26 | .classpath 27 | .project 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Intellij project files 33 | *.ipr 34 | *.iws 35 | .idea/ 36 | .gradle/ 37 | 38 | # Local configuration file (sdk path, etc) 39 | 40 | # Windows thumbnail db 41 | Thumbs.db 42 | 43 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 44 | 45 | build/ 46 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YModemForAndroid 2 | 3 | YModem For Android is a library that easy to transmit file data with some terminal devices like BloothLE using [ymodem protocol](https://en.wikipedia.org/wiki/YMODEM). 4 | 5 | ## Notice 6 | 7 | Though suitable, this library doesn't supply a ble component for transmitting data with terminal, it's your responsibility to encapsulate your own. 8 | 9 | ## Get Started 10 | 11 | Supported URI formats: 12 | ``` 13 | "file:///storage/emulated/0/filename.bin" // from SD card 14 | "assets://image.png" // from assets 15 | ``` 16 | 17 | ### Initiation 18 | ``` java 19 | ymodem = new YModem.Builder() 20 | .with(this) 21 | .filePath("assets://demo.bin") 22 | .fileName("demo.bin") 23 | .checkMd5("lsfjlhoiiw121241l241lgljaf") 24 | .callback(new YModemListener() { 25 | @Override 26 | public void onDataReady(byte[] data) { 27 | //send this data[] to your ble component here... 28 | } 29 | 30 | @Override 31 | public void onProgress(int currentSent, int total) { 32 | //the progress of the file data has transmitted 33 | } 34 | 35 | @Override 36 | public void onSuccess() { 37 | //we are well done with md5 checked 38 | } 39 | 40 | @Override 41 | public void onFailed(String reason) { 42 | //the task has failed for several times of trying 43 | } 44 | }).build(); 45 | 46 | ``` 47 | 48 | ### Start transmission 49 | ``` java 50 | ymodem.start(); 51 | ``` 52 | 53 | ### Received data from terminal 54 | When you received response from the ble terminal, tell ymodem to handle it: 55 | 56 | ``` java 57 | ymodem.onReceiveData(data); 58 | ``` 59 | The param data should be byte array. 60 | 61 | ### Stop 62 | ``` java 63 | ymodem.stop(); 64 | ``` 65 | Just enjoy it! 66 | 67 | ## About 68 | 69 | The concrete ymodem protocol implemented in this library: 70 | 71 | ``` 72 | * MY YMODEM IMPLEMTATION 73 | * *SENDER: ANDROID APP *------------------------------------------* RECEIVER: BLE DEVICE* 74 | * HELLO BOOTLOADER ---------------------------------------------->* 75 | * <---------------------------------------------------------------* C 76 | * SOH 00 FF filename0fileSizeInByte0MD5[90] ZERO[38] CRC CRC----->* 77 | * <---------------------------------------------------------------* ACK C 78 | * STX 01 FE data[1024] CRC CRC ---------------------------------->* 79 | * <---------------------------------------------------------------* ACK 80 | * STX 02 FF data[1024] CRC CRC ---------------------------------->* 81 | * <---------------------------------------------------------------* ACK 82 | * ... 83 | * ... 84 | *

85 | * STX 08 F7 data[1000] CPMEOF[24] CRC CRC ----------------------->* 86 | * <---------------------------------------------------------------* ACK 87 | * EOT ----------------------------------------------------------->* 88 | * <---------------------------------------------------------------* ACK 89 | * SOH 00 FF ZERO[128] ------------------------------------------->* 90 | * <---------------------------------------------------------------* ACK 91 | * <---------------------------------------------------------------* MD5_OK 92 | ``` 93 | 94 | ## Reference 95 | [Wikipedia YMODEM](https://en.wikipedia.org/wiki/YMODEM) 96 | [xmodem、ymodem、zmodem](http://web.cecs.pdx.edu/~rootd/catdoc/guide/TheGuide_226.html) 97 | [aesirot ymodem on github](https://github.com/aesirot/ymodem) 98 | 99 | ## License 100 | 101 | MIT -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.1" 6 | defaultConfig { 7 | applicationId "com.leonxtp.ymodemble" 8 | minSdkVersion 18 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:26.+' 28 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 29 | testCompile 'junit:junit:4.12' 30 | 31 | compile project(':library') 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/leonxtp/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/leonxtp/ymodemble/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.ymodemble; 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 | * Instrumentation 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.leonxtp.ymodemble", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/leonxtp/ymodemble/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.ymodemble; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | 7 | import com.leonxtp.library.YModemListener; 8 | import com.leonxtp.library.YModem; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | private YModem ymodem; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | } 19 | 20 | private void startTransmission() { 21 | 22 | ymodem = new YModem.Builder() 23 | .with(this) 24 | .filePath("assets://demo.bin") 25 | .fileName("demo.bin") 26 | .checkMd5("lsfjlhoiiw121241l241lgljaf") 27 | .callback(new YModemListener() { 28 | @Override 29 | public void onDataReady(byte[] data) { 30 | //send this data[] to your ble component here... 31 | } 32 | 33 | @Override 34 | public void onProgress(int currentSent, int total) { 35 | //the progress of the file data has transmitted 36 | } 37 | 38 | @Override 39 | public void onSuccess() { 40 | //we are well done with md5 checked 41 | } 42 | 43 | @Override 44 | public void onFailed(String reason) { 45 | //the task has failed for several times of trying 46 | } 47 | }).build(); 48 | 49 | ymodem.start(); 50 | } 51 | 52 | /** 53 | * When you received response from the ble terminal, tell ymodem 54 | */ 55 | public void onDataReceivedFromBLE(byte[] data) { 56 | ymodem.onReceiveData(data); 57 | } 58 | 59 | /*stop the transmission*/ 60 | public void onStopClick(View view) { 61 | ymodem.stop(); 62 | } 63 | 64 | @Override 65 | protected void onDestroy() { 66 | super.onDestroy(); 67 | //When the activity finished unexpected, just call stop ymodem 68 | ymodem.stop(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/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 | YModemBLE 3 | Oops! No Real ble component, use your own! 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/leonxtp/ymodemble/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.ymodemble; 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() throws Exception { 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 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /findbugs-android-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonXtp/YModemForAndroid/7b57a46e8825230ab158bd2bb977aaea0fce1ca2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Sep 17 10:00:25 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 18 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:26.+' 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/leonxtp/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/leonxtp/library/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 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 | * Instrumentation 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.leonxtp.library.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/CRC16.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 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 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 | -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/FileStreamThread.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 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 | * Created by leonxtp on 2017/9/16. 13 | * Modified by leonxtp on 2017/9/16 14 | */ 15 | 16 | public class FileStreamThread extends Thread { 17 | 18 | private Context mContext; 19 | private InputStream inputStream = null; 20 | private DataRaderListener listener; 21 | private String filePath; 22 | private AtomicBoolean isDataAcknowledged = new AtomicBoolean(false); 23 | private boolean isKeepRunning = false; 24 | private int fileByteSize = 0; 25 | 26 | public FileStreamThread(Context mContext, String filePath, DataRaderListener listener) { 27 | this.mContext = mContext; 28 | this.filePath = filePath; 29 | this.listener = listener; 30 | } 31 | 32 | public int getFileByteSize() throws IOException { 33 | if (fileByteSize == 0 || inputStream == null) { 34 | initStream(); 35 | } 36 | return fileByteSize; 37 | } 38 | 39 | @Override 40 | public void run() { 41 | try { 42 | prepareData(); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | 48 | private void prepareData() throws IOException { 49 | initStream(); 50 | byte[] block = new byte[1024]; 51 | int dataLength; 52 | byte blockSequence = 1;//The data package of a file is actually started from 1 53 | isDataAcknowledged.set(true); 54 | isKeepRunning = true; 55 | while (isKeepRunning) { 56 | 57 | if (!isDataAcknowledged.get()) { 58 | try { 59 | //We need to sleep for a while as the sending 1024 bytes data from ble would take several seconds 60 | //In my circumstances, this can be up to 3 seconds. 61 | Thread.sleep(100); 62 | } catch (InterruptedException e) { 63 | e.printStackTrace(); 64 | } 65 | continue; 66 | } 67 | 68 | if ((dataLength = inputStream.read(block)) == -1) { 69 | L.f("The file data has all been read..."); 70 | if (listener != null) { 71 | onStop(); 72 | listener.onFinish(); 73 | } 74 | break; 75 | } 76 | 77 | byte[] packige = YModemUtil.getDataPackage(block, dataLength, blockSequence); 78 | 79 | if (listener != null) { 80 | listener.onDataReady(packige); 81 | } 82 | 83 | blockSequence++; 84 | isDataAcknowledged.set(false); 85 | } 86 | 87 | } 88 | 89 | /** 90 | * When received response from the terminal ,we should keep the thread keep going 91 | */ 92 | public void keepReading() { 93 | isDataAcknowledged.set(true); 94 | } 95 | 96 | public void release() { 97 | onStop(); 98 | listener = null; 99 | } 100 | 101 | private void onStop() { 102 | isKeepRunning = false; 103 | isDataAcknowledged.set(false); 104 | fileByteSize = 0; 105 | onReadFinished(); 106 | } 107 | 108 | private void initStream() { 109 | if (inputStream == null) { 110 | try { 111 | inputStream = YModemUtil.getInputStream(mContext, filePath); 112 | fileByteSize = inputStream.available(); 113 | } catch (IOException e) { 114 | e.printStackTrace(); 115 | } 116 | } 117 | } 118 | 119 | private void onReadFinished() { 120 | if (inputStream != null) { 121 | try { 122 | inputStream.close(); 123 | inputStream = null; 124 | } catch (IOException e) { 125 | e.printStackTrace(); 126 | } 127 | } 128 | } 129 | 130 | public interface DataRaderListener { 131 | void onDataReady(byte[] data); 132 | 133 | void onFinish(); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/InputStreamSource.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | 10 | /** 11 | * Get InputStream from different source, files from sd card/assets supported. 12 | *

13 | * Created by leonxtp on 2017/9/17. 14 | * Modified by leonxtp on 2017/9/17 15 | */ 16 | 17 | public class InputStreamSource { 18 | 19 | private static final int BUFFER_SIZE = 32 * 1024; 20 | private static final String ERROR_UNSUPPORTED_SCHEME = "Unsupported file source"; 21 | 22 | InputStream getStream(Context context, String imageUri) throws IOException { 23 | switch (SourceScheme.ofUri(imageUri)) { 24 | 25 | case FILE: 26 | return getStreamFromFile(imageUri); 27 | 28 | case ASSETS: 29 | return getStreamFromAssets(context, imageUri); 30 | 31 | case UNKNOWN: 32 | default: 33 | return getStreamFromOtherSource(imageUri); 34 | } 35 | } 36 | 37 | private InputStream getStreamFromFile(String fileUri) throws IOException { 38 | String filePath = SourceScheme.FILE.crop(fileUri); 39 | return new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE); 40 | } 41 | 42 | private InputStream getStreamFromAssets(Context context, String fileUri) throws IOException { 43 | String filePath = SourceScheme.ASSETS.crop(fileUri); 44 | return context.getAssets().open(filePath); 45 | } 46 | 47 | private InputStream getStreamFromOtherSource(String fileUri) throws IOException { 48 | throw new UnsupportedOperationException(String.format(ERROR_UNSUPPORTED_SCHEME, fileUri)); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/L.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 2 | 3 | import android.util.Log; 4 | 5 | public class L { 6 | 7 | private static final String TAG = "YMODEM"; 8 | 9 | private L() { 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 | L.e(TAG, msg); 19 | } 20 | } 21 | 22 | // 下面四个是默认tag的函数 23 | public static void i(String msg) { 24 | if (DEBUGGING) 25 | L.i(TAG, msg); 26 | } 27 | 28 | public static void d(String msg) { 29 | if (DEBUGGING) 30 | L.d(TAG, msg); 31 | } 32 | 33 | public static void e(String msg) { 34 | if (DEBUGGING) 35 | L.e(TAG, msg); 36 | } 37 | 38 | public static void v(String msg) { 39 | if (DEBUGGING) 40 | L.v(TAG, msg); 41 | } 42 | 43 | public static void w(String msg) { 44 | if (DEBUGGING) 45 | L.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 | Log.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 | } -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/SourceScheme.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 2 | 3 | import java.util.Locale; 4 | 5 | /** 6 | * Thanks to Universal-Image-Loader: 7 | * https://github.com/nostra13/Android-Universal-Image-Loader/blob/master/library/src/main/java/com/nostra13/universalimageloader/core/download/ImageDownloader.java 8 | */ 9 | public enum SourceScheme { 10 | 11 | FILE("file"), ASSETS("assets"), UNKNOWN(""); 12 | 13 | private String scheme; 14 | private String uriPrefix; 15 | 16 | SourceScheme(String scheme) { 17 | this.scheme = scheme; 18 | uriPrefix = scheme + "://"; 19 | } 20 | 21 | /** 22 | * Defines scheme of incoming URI 23 | * 24 | * @param uri URI for scheme detection 25 | * @return SourceScheme of incoming URI 26 | */ 27 | public static SourceScheme ofUri(String uri) { 28 | if (uri != null) { 29 | for (SourceScheme s : values()) { 30 | if (s.belongsTo(uri)) { 31 | return s; 32 | } 33 | } 34 | } 35 | return UNKNOWN; 36 | } 37 | 38 | private boolean belongsTo(String uri) { 39 | return uri.toLowerCase(Locale.US).startsWith(uriPrefix); 40 | } 41 | 42 | /** 43 | * Removed scheme part ("scheme://") from incoming URI 44 | */ 45 | public String crop(String uri) { 46 | if (!belongsTo(uri)) { 47 | throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme)); 48 | } 49 | return uri.substring(uriPrefix.length()); 50 | } 51 | } -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/TimeOutHelper.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 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 | * Created by leonxtp on 2017/9/17. 9 | * Modified by leonxtp on 2017/9/17 10 | */ 11 | 12 | public class TimeOutHelper { 13 | 14 | private ITimeOut listener; 15 | 16 | private Handler timeoutHanldler = new Handler(); 17 | 18 | private Runnable timer = new Runnable() { 19 | 20 | @Override 21 | public void run() { 22 | stopTimer(); 23 | if (listener != null) { 24 | listener.onTimeOut(); 25 | } 26 | } 27 | }; 28 | 29 | public void startTimer(ITimeOut timeoutListener, long delay) { 30 | listener = timeoutListener; 31 | timeoutHanldler.postDelayed(timer, delay); 32 | } 33 | 34 | public void stopTimer() { 35 | timeoutHanldler.removeCallbacksAndMessages(null); 36 | } 37 | 38 | public void unRegisterListener() { 39 | listener = null; 40 | } 41 | 42 | public interface ITimeOut { 43 | void onTimeOut(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/YModem.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * THE YMODEM: 9 | * *SENDER: ANDROID APP *------------------------------------------* RECEIVER: BLE DEVICE* 10 | * HELLO BOOTLOADER ---------------------------------------------->* 11 | * <---------------------------------------------------------------* C 12 | * SOH 00 FF filename0fileSizeInByte0MD5[90] ZERO[38] CRC CRC----->* 13 | * <---------------------------------------------------------------* ACK C 14 | * STX 01 FE data[1024] CRC CRC ---------------------------------->* 15 | * <---------------------------------------------------------------* ACK 16 | * STX 02 FF data[1024] CRC CRC ---------------------------------->* 17 | * <---------------------------------------------------------------* ACK 18 | * ... 19 | * ... 20 | *

21 | * STX 08 F7 data[1000] CPMEOF[24] CRC CRC ----------------------->* 22 | * <---------------------------------------------------------------* ACK 23 | * EOT ----------------------------------------------------------->* 24 | * <---------------------------------------------------------------* ACK 25 | * SOH 00 FF ZERO[128] ------------------------------------------->* 26 | * <---------------------------------------------------------------* ACK 27 | * <---------------------------------------------------------------* MD5_OK 28 | *

29 | * Created by leonxtp on 2017/9/16. 30 | * Modified by leonxtp on 2017/9/16 31 | */ 32 | 33 | public class YModem implements FileStreamThread.DataRaderListener { 34 | 35 | private static final int STEP_HELLO = 0x00; 36 | private static final int STEP_FILE_NAME = 0x01; 37 | private static final int STEP_FILE_BODY = 0x02; 38 | private static final int STEP_EOT = 0x03; 39 | private static final int STEP_END = 0x04; 40 | private static int CURR_STEP = STEP_HELLO; 41 | 42 | private static final byte ACK = 0x06; /* ACKnowlege */ 43 | private static final byte NAK = 0x15; /* Negative AcKnowlege */ 44 | private static final byte CAN = 0x18; /* CANcel character */ 45 | private static final byte ST_C = 'C'; 46 | private static final String MD5_OK = "MD5_OK"; 47 | private static final String MD5_ERR = "MD5_ERR"; 48 | 49 | private Context mContext; 50 | private String filePath; 51 | private String fileNameString = "LPK001_Android"; 52 | private String fileMd5String = "63e7bb6eed1de3cece411a7e3e8e763b"; 53 | private YModemListener listener; 54 | 55 | private TimeOutHelper timerHelper = new TimeOutHelper(); 56 | private FileStreamThread streamThread; 57 | 58 | //bytes has been sent of this transmission 59 | private int bytesSent = 0; 60 | //package data of current sending, used for int case of fail 61 | private byte[] currSending = null; 62 | private int packageErrorTimes = 0; 63 | private static final int MAX_PACKAGE_SEND_ERROR_TIMES = 6; 64 | //the timeout interval for a single package 65 | private static final int PACKAGE_TIME_OUT = 6000; 66 | 67 | /** 68 | * Construct of the YModemBLE,you may don't need the fileMD5 checking,remove it 69 | * 70 | * @param filePath absolute path of the file 71 | * @param fileNameString file name for sending to the terminal 72 | * @param fileMd5String md5 for terminal checking after transmission finished 73 | * @param listener 74 | */ 75 | public YModem(Context context, String filePath, 76 | String fileNameString, String fileMd5String, 77 | YModemListener listener) { 78 | this.filePath = filePath; 79 | this.fileNameString = fileNameString; 80 | this.fileMd5String = fileMd5String; 81 | this.mContext = context; 82 | this.listener = listener; 83 | } 84 | 85 | /** 86 | * Start the transmission 87 | */ 88 | public void start() { 89 | sayHello(); 90 | } 91 | 92 | /** 93 | * Stop the transmission when you don't need it or shut it down in accident 94 | */ 95 | public void stop() { 96 | bytesSent = 0; 97 | currSending = null; 98 | packageErrorTimes = 0; 99 | if (streamThread != null) { 100 | streamThread.release(); 101 | } 102 | timerHelper.stopTimer(); 103 | timerHelper.unRegisterListener(); 104 | } 105 | 106 | /** 107 | * Method for the outer caller when received data from the terminal 108 | */ 109 | public void onReceiveData(byte[] respData) { 110 | //Stop the package timer 111 | timerHelper.stopTimer(); 112 | if (respData != null && respData.length > 0) { 113 | L.f("YModem received " + respData.length + " bytes."); 114 | switch (CURR_STEP) { 115 | case STEP_HELLO: 116 | handleHello(respData); 117 | break; 118 | case STEP_FILE_NAME: 119 | handleFileName(respData); 120 | break; 121 | case STEP_FILE_BODY: 122 | handleFileBody(respData); 123 | break; 124 | case STEP_EOT: 125 | handleEOT(respData); 126 | break; 127 | case STEP_END: 128 | handleEnd(respData); 129 | break; 130 | default: 131 | break; 132 | } 133 | } else { 134 | L.f("The terminal do responsed something, but received nothing??"); 135 | } 136 | } 137 | 138 | /** 139 | * ============================================================================== 140 | * Methods for sending data begin 141 | * ============================================================================== 142 | */ 143 | private void sayHello() { 144 | streamThread = new FileStreamThread(mContext, filePath, this); 145 | CURR_STEP = STEP_HELLO; 146 | L.f("sayHello!!!"); 147 | byte[] hello = YModemUtil.getYModelHello(); 148 | sendPackageData(hello); 149 | } 150 | 151 | private void sendFileName() { 152 | CURR_STEP = STEP_FILE_NAME; 153 | L.f("sendFileName"); 154 | try { 155 | int fileByteSize = streamThread.getFileByteSize(); 156 | byte[] fileNamePackage = YModemUtil.getFileNamePackage(fileNameString, fileByteSize 157 | , fileMd5String); 158 | sendPackageData(fileNamePackage); 159 | } catch (IOException e) { 160 | e.printStackTrace(); 161 | } 162 | } 163 | 164 | private void startSendFileData() { 165 | CURR_STEP = STEP_FILE_BODY; 166 | L.f("startSendFileData"); 167 | streamThread.start(); 168 | } 169 | 170 | //Callback from the data reading thread when a data package is ready 171 | @Override 172 | public void onDataReady(byte[] data) { 173 | sendPackageData(data); 174 | } 175 | 176 | private void sendEOT() { 177 | CURR_STEP = STEP_EOT; 178 | L.f("sendEOT"); 179 | if (listener != null) { 180 | listener.onDataReady(YModemUtil.getEOT()); 181 | } 182 | } 183 | 184 | private void sendEND() { 185 | CURR_STEP = STEP_END; 186 | L.f("sendEND"); 187 | if (listener != null) { 188 | try { 189 | listener.onDataReady(YModemUtil.getEnd()); 190 | } catch (IOException e) { 191 | e.printStackTrace(); 192 | } 193 | } 194 | } 195 | 196 | private void sendPackageData(byte[] packageData) { 197 | if (listener != null && packageData != null) { 198 | currSending = packageData; 199 | //Start the timer, it will be cancelled when reponse received, 200 | // or trigger the timeout and resend the current package data 201 | timerHelper.startTimer(timeoutListener, PACKAGE_TIME_OUT); 202 | listener.onDataReady(packageData); 203 | } 204 | } 205 | 206 | /** 207 | * ============================================================================== 208 | * Method for handling the response of a package 209 | * ============================================================================== 210 | */ 211 | private void handleHello(byte[] value) { 212 | int character = value[0]; 213 | if (character == ST_C) {//Receive "C" for "HELLO" 214 | L.f("Received 'C'"); 215 | packageErrorTimes = 0; 216 | sendFileName(); 217 | } else { 218 | handleOthers(character); 219 | } 220 | } 221 | 222 | //The file name package was responsed 223 | private void handleFileName(byte[] value) { 224 | if (value.length == 2 && value[0] == ACK && value[1] == ST_C) {//Receive 'ACK C' for file name 225 | L.f("Received 'ACK C'"); 226 | packageErrorTimes = 0; 227 | startSendFileData(); 228 | } else if (value[0] == ST_C) {//Receive 'C' for file name, this package should be resent 229 | L.f("Received 'C'"); 230 | handlePackageFail("Received 'C' without 'ACK' after sent file name"); 231 | } else { 232 | handleOthers(value[0]); 233 | } 234 | } 235 | 236 | private void handleFileBody(byte[] value) { 237 | if (value.length == 1 && value[0] == ACK) {//Receive ACK for file data 238 | L.f("Received 'ACK'"); 239 | packageErrorTimes = 0; 240 | bytesSent += currSending.length; 241 | try { 242 | if (listener != null) { 243 | listener.onProgress(bytesSent, streamThread.getFileByteSize()); 244 | } 245 | } catch (IOException e) { 246 | e.printStackTrace(); 247 | } 248 | streamThread.keepReading(); 249 | 250 | } else if (value.length == 1 && value[0] == ST_C) { 251 | L.f("Received 'C'"); 252 | //Receive C for file data, the ymodem cannot handle this circumstance, transmission failed... 253 | handlePackageFail("Received 'C' after sent file data"); 254 | } else { 255 | handleOthers(value[0]); 256 | } 257 | } 258 | 259 | private void handleEOT(byte[] value) { 260 | if (value[0] == ACK) { 261 | L.f("Received 'ACK'"); 262 | packageErrorTimes = 0; 263 | sendEND(); 264 | } else if (value[0] == ST_C) {//As we haven't received ACK, we should resend EOT 265 | handlePackageFail("Received 'C' after sent EOT"); 266 | } else { 267 | handleOthers(value[0]); 268 | } 269 | } 270 | 271 | private void handleEnd(byte[] character) { 272 | if (character[0] == ACK) {//The last ACK represents that the transmission has been finished, but we should validate the file 273 | L.f("Received 'ACK'"); 274 | packageErrorTimes = 0; 275 | } else if ((new String(character)).equals(MD5_OK)) {//The file data has been checked,Well Done! 276 | L.f("Received 'MD5_OK'"); 277 | stop(); 278 | if (listener != null) { 279 | listener.onSuccess(); 280 | } 281 | } else if ((new String(character)).equals(MD5_ERR)) {//Oops...Transmission Failed... 282 | L.f("Received 'MD5_ERR'"); 283 | stop(); 284 | if (listener != null) { 285 | listener.onFailed("MD5 check failed!!!"); 286 | } 287 | } else { 288 | handleOthers(character[0]); 289 | } 290 | } 291 | 292 | private void handleOthers(int character) { 293 | if (character == NAK) {//We need to resend this package as the terminal failed when checking the crc 294 | L.f("Received 'NAK'"); 295 | handlePackageFail("Received NAK"); 296 | } else if (character == CAN) {//Some big problem occurred, transmission failed... 297 | L.f("Received 'CAN'"); 298 | if (listener != null) { 299 | listener.onFailed("Received CAN"); 300 | } 301 | stop(); 302 | } 303 | } 304 | 305 | //Handle a failed package data ,resend it up to MAX_PACKAGE_SEND_ERROR_TIMES times. 306 | //If still failed, then the transmission failed. 307 | private void handlePackageFail(String reason) { 308 | packageErrorTimes++; 309 | L.f("Fail:" + reason + " for " + packageErrorTimes + " times"); 310 | if (packageErrorTimes < MAX_PACKAGE_SEND_ERROR_TIMES) { 311 | sendPackageData(currSending); 312 | } else { 313 | //Still, we stop the transmission, release the resources 314 | stop(); 315 | if (listener != null) { 316 | listener.onFailed(reason); 317 | } 318 | } 319 | } 320 | 321 | /* The InputStream data reading thread was done */ 322 | @Override 323 | public void onFinish() { 324 | sendEOT(); 325 | } 326 | 327 | //The timeout listener 328 | private TimeOutHelper.ITimeOut timeoutListener = new TimeOutHelper.ITimeOut() { 329 | @Override 330 | public void onTimeOut() { 331 | L.f("------ time out ------"); 332 | if (currSending != null) { 333 | handlePackageFail("package timeout..."); 334 | } 335 | } 336 | }; 337 | 338 | public static class Builder { 339 | private Context context; 340 | private String filePath; 341 | private String fileNameString; 342 | private String fileMd5String; 343 | private YModemListener listener; 344 | 345 | public Builder with(Context context) { 346 | this.context = context; 347 | return this; 348 | } 349 | 350 | public Builder filePath(String filePath) { 351 | this.filePath = filePath; 352 | return this; 353 | } 354 | 355 | public Builder fileName(String fileName) { 356 | this.fileNameString = fileName; 357 | return this; 358 | } 359 | 360 | public Builder checkMd5(String fileMd5String) { 361 | this.fileMd5String = fileMd5String; 362 | return this; 363 | } 364 | 365 | public Builder callback(YModemListener listener) { 366 | this.listener = listener; 367 | return this; 368 | } 369 | 370 | public YModem build() { 371 | return new YModem(context, filePath, fileNameString, fileMd5String, listener); 372 | } 373 | 374 | } 375 | 376 | } 377 | -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/YModemListener.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 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 | } -------------------------------------------------------------------------------- /library/src/main/java/com/leonxtp/library/YModemUtil.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Arrays; 10 | 11 | /** 12 | * Util for encapsulating data package of ymodem protocol 13 | *

14 | * Created by leonxtp on 2017/9/16. 15 | * Modified by leonxtp on 2017/9/16 16 | */ 17 | 18 | public class YModemUtil { 19 | 20 | /*This is my concrete ymodem start signal, customise it to your needs*/ 21 | private static final String HELLO = "HELLO BOOTLOADER"; 22 | 23 | private static final byte SOH = 0x01; /* Start Of Header with data size :128*/ 24 | private static final byte STX = 0x02; /* Start Of Header with data size : 1024*/ 25 | private static final byte EOT = 0x04; /* End Of Transmission */ 26 | private static final byte CPMEOF = 0x1A;/* Fill the last package if not long enough */ 27 | 28 | private static CRC16 crc16 = new CRC16(); 29 | 30 | /** 31 | * Get the first package data for hello with a terminal 32 | */ 33 | public static byte[] getYModelHello() { 34 | return HELLO.getBytes(); 35 | } 36 | 37 | /** 38 | * Get the file name package data 39 | * 40 | * @param fileNameString file name in String 41 | * @param fileByteSize file byte size of int value 42 | * @param fileMd5String the md5 of the file in String 43 | */ 44 | public static byte[] getFileNamePackage(String fileNameString, 45 | int fileByteSize, 46 | String fileMd5String) throws IOException { 47 | 48 | byte seperator = 0x0; 49 | String fileSize = fileByteSize + ""; 50 | byte[] byteFileSize = fileSize.getBytes(); 51 | 52 | byte[] fileNameBytes1 = concat(fileNameString.getBytes(), 53 | new byte[]{seperator}, 54 | byteFileSize); 55 | 56 | byte[] fileNameBytes2 = Arrays.copyOf(concat(fileNameBytes1, 57 | new byte[]{seperator}, 58 | fileMd5String.getBytes()), 128); 59 | 60 | byte seq = 0x00; 61 | return getDataPackage(fileNameBytes2, 128, seq); 62 | } 63 | 64 | /** 65 | * Get a encapsulated package data block 66 | * 67 | * @param block byte data array 68 | * @param dataLength the actual content length in the block without 0 filled in it. 69 | * @param sequence the package serial number 70 | * @return a encapsulated package data block 71 | */ 72 | public static byte[] getDataPackage(byte[] block, int dataLength, byte sequence) throws IOException { 73 | 74 | byte[] header = getDataHeader(sequence, block.length == 1024 ? STX : SOH); 75 | 76 | //The last package, fill CPMEOF if the dataLength is not sufficient 77 | if (dataLength < block.length) { 78 | int startFil = dataLength; 79 | while (startFil < block.length) { 80 | block[startFil] = CPMEOF; 81 | startFil++; 82 | } 83 | } 84 | 85 | //We should use short size when writing into the data package as it only needs 2 bytes 86 | short crc = (short) crc16.calcCRC(block); 87 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 88 | DataOutputStream dos = new DataOutputStream(baos); 89 | dos.writeShort(crc); 90 | dos.close(); 91 | 92 | byte[] crcBytes = baos.toByteArray(); 93 | 94 | return concat(header, block, crcBytes); 95 | } 96 | 97 | /** 98 | * Get the EOT package 99 | */ 100 | public static byte[] getEOT() { 101 | return new byte[]{EOT}; 102 | } 103 | 104 | /** 105 | * Get the Last package 106 | */ 107 | public static byte[] getEnd() throws IOException { 108 | byte seq = 0x00; 109 | return getDataPackage(new byte[128], 128, seq); 110 | } 111 | 112 | /** 113 | * Get InputStream from Assets, you can customize it from the other sources 114 | * 115 | * @param fileAbsolutePath absolute path of the file in asstes 116 | */ 117 | public static InputStream getInputStream(Context context, String fileAbsolutePath) throws IOException { 118 | return new InputStreamSource().getStream(context, fileAbsolutePath); 119 | } 120 | 121 | private static byte[] getDataHeader(byte sequence, byte start) { 122 | //The serial number of the package increases Cyclically up to 256 123 | byte modSequence = (byte) (sequence % 0x256); 124 | byte complementSeq = (byte) ~modSequence; 125 | 126 | return concat(new byte[]{start}, 127 | new byte[]{modSequence}, 128 | new byte[]{complementSeq}); 129 | } 130 | 131 | private static byte[] concat(byte[] a, byte[] b, byte[] c) { 132 | int aLen = a.length; 133 | int bLen = b.length; 134 | int cLen = c.length; 135 | byte[] concated = new byte[aLen + bLen + cLen]; 136 | System.arraycopy(a, 0, concated, 0, aLen); 137 | System.arraycopy(b, 0, concated, aLen, bLen); 138 | System.arraycopy(c, 0, concated, aLen + bLen, cLen); 139 | return concated; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Library 3 | 4 | -------------------------------------------------------------------------------- /library/src/test/java/com/leonxtp/library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.leonxtp.library; 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() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------