From 38ee7c490d7d8fab05c832b25e94d4b7a10b568a Mon Sep 17 00:00:00 2001 From: Adrian Rumpold Date: Sun, 11 Jun 2017 11:02:11 +0200 Subject: [PATCH] Initial revision --- .gitignore | 55 ++++ .idea/.name | 1 + .idea/compiler.xml | 21 ++ .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 6 + .idea/misc.xml | 246 +++++++++++++++ .idea/modules.xml | 9 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 27 ++ app/proguard-rules.pro | 17 ++ .../rumpold/androiddsky/ApplicationTest.java | 13 + app/src/main/AndroidManifest.xml | 25 ++ .../java/de/rumpold/androiddsky/DSKY.java | 260 ++++++++++++++++ .../androiddsky/network/AgcPacket.java | 119 ++++++++ .../androiddsky/network/YaAgcClient.java | 285 ++++++++++++++++++ .../rumpold/androiddsky/ui/DSKYActivity.java | 243 +++++++++++++++ app/src/main/res/layout/activity_dsky.xml | 47 +++ app/src/main/res/layout/layout_7seg.xml | 212 +++++++++++++ .../res/layout/layout_indicator_lights.xml | 128 ++++++++ app/src/main/res/layout/layout_keyboard.xml | 189 ++++++++++++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes app/src/main/res/values/attrs.xml | 12 + app/src/main/res/values/colors.xml | 12 + app/src/main/res/values/dsky_labels.xml | 14 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 53 ++++ .../rumpold/androiddsky/ExampleUnitTest.java | 15 + build.gradle | 23 ++ gradle.properties | 18 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 ++++++++++ gradlew.bat | 90 ++++++ settings.gradle | 1 + 39 files changed, 2326 insertions(+) create mode 100755 .gitignore create mode 100755 .idea/.name create mode 100755 .idea/compiler.xml create mode 100755 .idea/copyright/profiles_settings.xml create mode 100755 .idea/encodings.xml create mode 100755 .idea/misc.xml create mode 100755 .idea/modules.xml create mode 100755 .idea/runConfigurations.xml create mode 100755 app/.gitignore create mode 100755 app/build.gradle create mode 100755 app/proguard-rules.pro create mode 100755 app/src/androidTest/java/de/rumpold/androiddsky/ApplicationTest.java create mode 100755 app/src/main/AndroidManifest.xml create mode 100755 app/src/main/java/de/rumpold/androiddsky/DSKY.java create mode 100755 app/src/main/java/de/rumpold/androiddsky/network/AgcPacket.java create mode 100755 app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java create mode 100755 app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java create mode 100755 app/src/main/res/layout/activity_dsky.xml create mode 100755 app/src/main/res/layout/layout_7seg.xml create mode 100755 app/src/main/res/layout/layout_indicator_lights.xml create mode 100755 app/src/main/res/layout/layout_keyboard.xml create mode 100755 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100755 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100755 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100755 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100755 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100755 app/src/main/res/values/attrs.xml create mode 100755 app/src/main/res/values/colors.xml create mode 100755 app/src/main/res/values/dsky_labels.xml create mode 100755 app/src/main/res/values/strings.xml create mode 100755 app/src/main/res/values/styles.xml create mode 100755 app/src/test/java/de/rumpold/androiddsky/ExampleUnitTest.java create mode 100755 build.gradle create mode 100755 gradle.properties create mode 100755 gradle/wrapper/gradle-wrapper.jar create mode 100755 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100755 gradlew.bat create mode 100755 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..520a863 --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries + +# Keystore files +*.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json diff --git a/.idea/.name b/.idea/.name new file mode 100755 index 0000000..936c2fb --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +androidDSKY \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100755 index 0000000..1f2af51 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100755 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100755 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100755 index 0000000..f45ff62 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + Abstraction issuesJava + + + Android Lint + + + Assignment issuesJava + + + Bitwise operation issuesJava + + + Class metricsJava + + + Class structureJava + + + Cloning issuesJava + + + Code maturity issuesJava + + + Code style issuesJava + + + Compiler issuesJava + + + Concurrency annotation issuesJava + + + Control flow issuesJava + + + Data flow issuesJava + + + Declaration redundancyJava + + + Dependency issuesJava + + + Encapsulation issuesJava + + + Error handlingJava + + + Finalization issuesJava + + + GPath inspectionsGroovy + + + General + + + GeneralJava + + + Google Cloud Endpoints + + + Groovy + + + ImportsJava + + + Inheritance issuesJava + + + Initialization issuesJava + + + Internationalization issues + + + Internationalization issuesJava + + + J2ME issuesJava + + + JUnit issues + + + JUnit issuesJava + + + Java + + + Java language level issuesJava + + + Java language level migration aidsJava + + + JavaBeans issuesJava + + + JavaFX + + + Javadoc issuesJava + + + Language Injection + + + Logging issuesJava + + + Manifest + + + Memory issuesJava + + + Method metricsJava + + + Modularization issuesJava + + + Naming ConventionsGroovy + + + Naming conventionsJava + + + Numeric issuesJava + + + Packaging issuesJava + + + Performance issuesJava + + + Portability issuesJava + + + Probable bugsGroovy + + + Probable bugsJava + + + Properties Files + + + Properties FilesJava + + + Resource management issuesJava + + + Security issuesJava + + + Serialization issuesJava + + + StyleGroovy + + + TestNG + + + Threading issuesGroovy + + + Threading issuesJava + + + Verbose or redundant code constructsJava + + + Visibility issuesJava + + + toString() issuesJava + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100755 index 0000000..281b77b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100755 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100755 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100755 index 0000000..26a4072 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "rumpold.de.androiddsky" + minSdkVersion 21 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.4.0' + compile 'com.android.support:support-v4:23.4.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100755 index 0000000..a8bf777 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\Adriano\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/de/rumpold/androiddsky/ApplicationTest.java b/app/src/androidTest/java/de/rumpold/androiddsky/ApplicationTest.java new file mode 100755 index 0000000..61d9cef --- /dev/null +++ b/app/src/androidTest/java/de/rumpold/androiddsky/ApplicationTest.java @@ -0,0 +1,13 @@ +package de.rumpold.androiddsky; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..7d1c75d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/de/rumpold/androiddsky/DSKY.java b/app/src/main/java/de/rumpold/androiddsky/DSKY.java new file mode 100755 index 0000000..d5b9dc3 --- /dev/null +++ b/app/src/main/java/de/rumpold/androiddsky/DSKY.java @@ -0,0 +1,260 @@ +package de.rumpold.androiddsky; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import de.rumpold.androiddsky.network.YaAgcClient; +import de.rumpold.androiddsky.ui.DSKYActivity; + +/** + * Created by Adriano on 17.05.2016. + */ +public class DSKY { + public static final String TAG = "TAG"; + public static final int UPDATE_RATE = 25; + + private final DSKYActivity activity; + private final YaAgcClient client; + + private String prog; + private String verb; + private String noun; + + private char signR1; + private char signR2; + private char signR3; + private String[] register1 = new String[5]; + private String[] register2 = new String[5]; + private String[] register3 = new String[5]; + + private boolean compActy; + private boolean uplinkActy; + private boolean noAtt; + private boolean gimbalLock; + private boolean tracker; + private boolean temp; + private boolean vel; + private boolean alt; + private boolean progError; + private boolean keyRel; + private boolean oprErr; + private boolean stby; + private boolean restart; + private final ScheduledThreadPoolExecutor executor; + + private final Map keycodeMap = new HashMap<>(); + { + keycodeMap.put("0", 16); + keycodeMap.put("1", 1); + keycodeMap.put("2", 2); + keycodeMap.put("3", 3); + keycodeMap.put("4", 4); + keycodeMap.put("5", 5); + keycodeMap.put("6", 6); + keycodeMap.put("7", 7); + keycodeMap.put("8", 8); + keycodeMap.put("9", 9); + keycodeMap.put("VERB", 17); + keycodeMap.put("RSET", 18); + keycodeMap.put("KEY\nREL", 25); + keycodeMap.put("+", 26); + keycodeMap.put("-", 27); + keycodeMap.put("ENTR", 28); + keycodeMap.put("CLR", 30); + keycodeMap.put("NOUN", 31); + } + + public DSKY(DSKYActivity activity) { + this.activity = activity; + this.client = new YaAgcClient(this); + + this.executor = new ScheduledThreadPoolExecutor(1); + + Arrays.fill(register1, ""); + Arrays.fill(register2, ""); + Arrays.fill(register3, ""); + } + + public void connect() { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + try { + client.connect(); + } catch (IOException e) { + throw new RuntimeException("Cannot connect to yaAGC", e); + } + } + }); + + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + activity.updateIndicator(R.id.lbl_gimbalLock, gimbalLock); + activity.updateIndicator(R.id.lbl_noAtt, noAtt); + activity.updateIndicator(R.id.lbl_uplinkActy, uplinkActy); + activity.updateIndicator(R.id.lbl_prog, progError); + activity.updateIndicator(R.id.lbl_tracker, tracker); + activity.updateIndicator(R.id.lbl_oprErr, oprErr); + activity.updateIndicator(R.id.lbl_stby, stby); + activity.updateIndicator(R.id.lbl_keyRel, keyRel); + activity.updateIndicator(R.id.lbl_restart, restart); + + activity.updateIndicator(R.id.lbl_compActy, compActy); + activity.updateDisplay(R.id.lbl_progId, prog); + activity.updateDisplay(R.id.lbl_verb, verb); + activity.updateDisplay(R.id.lbl_noun, noun); + + activity.updateDisplay(R.id.lbl_R1, concat(register1)); + activity.updateDisplay(R.id.lbl_R2, concat(register2)); + activity.updateDisplay(R.id.lbl_R3, concat(register3)); + + + activity.updateDisplay(R.id.lbl_sign_R1, String.valueOf(signR1)); + activity.updateDisplay(R.id.lbl_sign_R2, String.valueOf(signR2)); + activity.updateDisplay(R.id.lbl_sign_R3, String.valueOf(signR3)); + } + }, UPDATE_RATE, UPDATE_RATE, TimeUnit.MILLISECONDS); + } + + private String concat(String[] array) { + final StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < array.length; i++) { + buffer.append(array[i]); + } + return buffer.toString(); + } + + public void disconnect() { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + try { + client.disconnect(); + } catch (IOException e) { + throw new RuntimeException("Cannot connect to yaAGC", e); + } + } + }); + + executor.shutdownNow(); + } + + public void setCompActy(boolean compActy) { + this.compActy = compActy; + } + + public void setGimbalLock(boolean gimbalLock) { + this.gimbalLock = gimbalLock; + } + + public void setKeyRel(boolean keyRel) { + this.keyRel = keyRel; + } + + public void setNoAtt(boolean noAtt) { + this.noAtt = noAtt; + } + + public void setOprErr(boolean oprErr) { + this.oprErr = oprErr; + } + + public void setProgError(boolean progError) { + this.progError = progError; + } + + public void setTemp(boolean temp) { + this.temp = temp; + } + + public void setAlt(boolean alt) { + this.alt = alt; + } + + public void setVel(boolean vel) { + this.vel = vel; + } + + public void setTracker(boolean tracker) { + this.tracker = tracker; + } + + public void setUplinkActy(boolean uplinkActy) { + this.uplinkActy = uplinkActy; + } + + public void setStby(boolean stby) { + this.stby = stby; + } + + public void setRestart(boolean restart) { + this.restart = restart; + } + + public void setNoun(String noun) { + this.noun = noun; + } + + public void setProg(String prog) { + this.prog = prog; + } + + public void setVerb(String verb) { + this.verb = verb; + } + + public void setRegister1(int index, String digit) { + Log.d(TAG, "setRegister1: " + index + ", " + digit); + this.register1[index] = digit; + } + + public void setRegister2(int index, String digit) { + Log.d(TAG, "setRegister2: " + index + ", " + digit); + this.register2[index] = digit; + } + + public void setRegister3(int index, String digit) { + Log.d(TAG, "setRegister3: " + index + ", " + digit); + this.register3[index] = digit; + } + + public void setSignR1(char signR1) { + this.signR1 = signR1; + } + + public void setSignR2(char signR2) { + this.signR2 = signR2; + } + + public void setSignR3(char signR3) { + this.signR3 = signR3; + } + + public void buttonPressed(final String button) { + Log.d(TAG, "buttonPressed: " + button); + + AsyncTask.execute(new Runnable() { + @Override + public void run() { + try { + if ("PRO".equals(button)) { + client.sendProceed(); + } else { + final Integer keyCode = keycodeMap.get(button); + client.sendKeyCode(keyCode); + } + } catch (IOException e) { + Log.w(TAG, "buttonPressed: Could not send key press", e); + } + } + }); + } +} diff --git a/app/src/main/java/de/rumpold/androiddsky/network/AgcPacket.java b/app/src/main/java/de/rumpold/androiddsky/network/AgcPacket.java new file mode 100755 index 0000000..aa3df89 --- /dev/null +++ b/app/src/main/java/de/rumpold/androiddsky/network/AgcPacket.java @@ -0,0 +1,119 @@ +package de.rumpold.androiddsky.network; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Created by Adriano on 17.05.2016. + */ +public class AgcPacket { + public static final int PACKET_LENGTH = 4; + + private final boolean u; + private final boolean t; + private final int ioPort; + private final int dataCode; + + private AgcPacket(int dataCode, boolean u, boolean t, int ioPort) { + this.dataCode = dataCode; + this.u = u; + this.t = t; + this.ioPort = ioPort; + } + + /** + * The format is quite simple: each data packet consists of 4 bytes, + * arranged in bit-fields as follows (in order of transmission): + * + *
00utpppp 01pppddd 10dddddd 11dddddd
+ * + * @param bytes + */ + public static AgcPacket parseAgcPacket(ByteBuffer bytes) { + try { + bytes.rewind(); + + /*final StringBuilder sb = new StringBuilder(); + for (byte b : bytes.array()) { + sb.append(String.format("%8s", Integer.toBinaryString(((int) b) & 0xff)).replace(' ', '0')); + sb.append(' '); + } + Log.d("AGCPacket", "AgcPacket: " + sb.toString());*/ + + bytes.order(ByteOrder.BIG_ENDIAN); + if (bytes.remaining() != PACKET_LENGTH) { + // invalid packet length + throw new IllegalArgumentException("packet length"); + } + + /* PARSE BYTE #1 */ + int byteNum = 0; + char c = (char) ((short) bytes.get() & 0xff); + validateByteHeader(c, byteNum); + + // Bit 'u': + boolean u = (c & 0b0010_0000) > 0; + boolean t = (c & 0b0001_0000) > 0; + int ioPort = c & 0b0000_1111; + + /* PARSE BYTE #2 */ + ++byteNum; + c = (char) ((short) bytes.get() & 0xff); + validateByteHeader(c, byteNum); + + ioPort = (ioPort << 3) | (c & 0b0011_1000) >> 3; + int dataCode = (c & 0b0000_0111); + + /* PARSE BYTE #3 */ + ++byteNum; + c = (char) ((short) bytes.get() & 0xff); + validateByteHeader(c, byteNum); + + dataCode = (dataCode << 6) | (c & 0b0011_1111); + + /* PARSE BYTE #4 */ + ++byteNum; + c = (char) ((short) bytes.get() & 0xff); + validateByteHeader(c, byteNum); + + dataCode = (dataCode << 6) | (c & 0b0011_1111); + + return new AgcPacket(dataCode, u, t, ioPort); + } catch (Exception e) { + return null; + } + } + + private static void validateByteHeader(char c, int byteNum) { + if ((c & 0b1100_0000) >> 6 != byteNum) { + // invalid byte header + throw new IllegalArgumentException("packet byte header in byte #" + byteNum); + } + } + + public int getDataCode() { + return dataCode; + } + + public int getIoPort() { + return ioPort; + } + + public boolean t() { + return t; + } + + public boolean u() { + return u; + } + + @Override + public String toString() { + return "AgcPacket{" + + "dataCode=" + dataCode + + ", ioPort=" + Integer.toOctalString(ioPort) + + ", u=" + u + + ", t=" + t + + '}'; + } +} diff --git a/app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java b/app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java new file mode 100755 index 0000000..cfe4e1b --- /dev/null +++ b/app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java @@ -0,0 +1,285 @@ +package de.rumpold.androiddsky.network; + +import android.util.Log; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import de.rumpold.androiddsky.DSKY; + +/** + * Created by Adriano on 17.05.2016. + */ +public class YaAgcClient { + public static final int YAAGC_SERVER_PORT = 19698; + private static final String TAG = "YaAGC client"; + public static final int KEYBOARD_CHANNEL = 015; + private final DSKY dsky; + + private SocketChannel agcChannel; + private Thread handler; + + private class DSKYHandler implements Runnable { + @Override + public void run() { + final ByteBuffer buf = ByteBuffer.allocate(4); + while (!Thread.interrupted()) { + try { + buf.rewind(); + if (!agcChannel.isConnected()) { + Thread.sleep(500); + continue; + } + + agcChannel.read(buf); + buf.rewind(); + + handleAgcPacket(AgcPacket.parseAgcPacket(buf)); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + public YaAgcClient(DSKY dsky) { + this.dsky = dsky; + } + + public void connect() throws IOException { + agcChannel = SocketChannel.open(); + agcChannel.connect(new InetSocketAddress("137.250.170.198", YAAGC_SERVER_PORT)); + + handler = new Thread(new DSKYHandler(), "DSKY I/O Handler"); + handler.start(); + } + + public void disconnect() throws IOException { + if (handler != null) { + handler.interrupt(); + try { + handler.join(); + } catch (InterruptedException e) { + // expected + } + } + agcChannel.close(); + } + + + public void sendProceed() throws IOException { + sendChannelOutput(KEYBOARD_CHANNEL, 1 << 13); + } + + public void sendKeyCode(int keyCode) throws IOException { + sendChannelOutput(KEYBOARD_CHANNEL, keyCode); + } + + protected void sendChannelOutput(int channel, int data) throws IOException { + final byte[] packet = new byte[]{ + (byte) (((channel >> 3) & 0b1111) & 0xff), + (byte) ((0b0100_0000 | (channel & 0b111) << 3 | ((data >> 12) & 0b111)) & 0xff), + (byte) ((0b1000_0000 | ((data >> 6) & 0b111_111)) & 0xff), + (byte) ((0b1100_0000 | (data & 0b111_111)) & 0xff) + }; + + final ByteBuffer buffer = ByteBuffer.wrap(packet); + buffer.rewind(); + agcChannel.write(buffer); + } + + protected String extractDigit(int digitCode) { + switch (digitCode) { + case 0: + return " "; + + case 21: + return "0"; + + case 3: + return "1"; + + case 25: + return "2"; + + case 27: + return "3"; + + case 15: + return "4"; + + case 30: + return "5"; + + case 28: + return "6"; + + case 19: + return "7"; + + case 29: + return "8"; + + case 31: + return "9"; + + default: + throw new IllegalArgumentException("Invalid TAG digit code: " + digitCode); + } + + } + + protected void handleAgcPacket(AgcPacket packet) { + if (packet == null) { + return; + } + + switch (packet.getIoPort()) { + case 010: { + // TAG 7-segment displays + final int digitIdx = (packet.getDataCode() >> 11) & 0b1111; + final int data = packet.getDataCode() & 0b1_11111_11111; + + final int b = (data >> 10) & 0b1; + final int c = (data >> 5) & 0b11111; + final int d = data & 0b11111; + + switch (digitIdx) { + case 11: { + // PROG digits + + final String prog = extractDigit(c) + extractDigit(d); + dsky.setProg(prog); + + Log.d(TAG, "[DSKY PROG] " + prog); + break; + } + + case 10: { + // Verb digits + final String verb = extractDigit(c) + extractDigit(d); + dsky.setVerb(verb); + + Log.d(TAG, "[DSKY VERB] " + verb); + break; + } + + case 9: { + // Noun digits + final String noun = extractDigit(c) + extractDigit(d); + dsky.setNoun(noun); + + Log.d(TAG, "[DSKY NOUN] " + noun); + break; + } + + case 8: { + // First digit of R1 + dsky.setRegister1(0, extractDigit(d)); + break; + } + + case 7: { + // 2nd & 3rd digit of R1, + sign R1 + dsky.setRegister1(1, extractDigit(c)); + dsky.setRegister1(2, extractDigit(d)); + dsky.setSignR1(b > 0 ? '+' : ' '); + break; + } + + case 6: { + // 4th & 5th digit of R1, - sign R1 + dsky.setRegister1(3, extractDigit(c)); + dsky.setRegister1(4, extractDigit(d)); + dsky.setSignR1(b > 0 ? '-' : ' '); + break; + } + + case 5: { + // 1st & 2nd digit of R2, + sign R2 + dsky.setRegister2(0, extractDigit(c)); + dsky.setRegister2(1, extractDigit(d)); + dsky.setSignR2(b > 0 ? '+' : ' '); + break; + } + + case 4: { + // 3rd & 4th digit of R2, - sign R2 + dsky.setRegister2(2, extractDigit(c)); + dsky.setRegister2(3, extractDigit(d)); + dsky.setSignR2(b > 0 ? '-' : ' '); + break; + } + + case 3: { + // 5th digit of R2, 1st digit of R3 + dsky.setRegister2(4, extractDigit(c)); + dsky.setRegister3(0, extractDigit(d)); + break; + } + + case 2: { + // 2nd & 3rd digit of R3, + sign R3 + dsky.setRegister3(1, extractDigit(c)); + dsky.setRegister3(2, extractDigit(d)); + dsky.setSignR3(b > 0 ? '+' : ' '); + break; + } + + case 1: { + // 4th & 5th digit of R3, - sign R3 + dsky.setRegister3(3, extractDigit(c)); + dsky.setRegister3(4, extractDigit(d)); + dsky.setSignR3(b > 0 ? '-' : ' '); + break; + } + + case 12: { + // Indicators + final boolean vel = (data & 0b0_00000_00100) > 0; + final boolean noAtt = (data & 0b0_00000_01000) > 0; + final boolean alt = (data & 0b0_00000_10000) > 0; + final boolean gimbalLock = (data & 0b0_00001_00000) > 0; + final boolean tracker = (data & 0b0_00100_00000) > 0; + final boolean prog = (data & 0b0_01000_00000) > 0; + + dsky.setVel(vel); + dsky.setNoAtt(noAtt); + dsky.setAlt(alt); + dsky.setGimbalLock(gimbalLock); + dsky.setTracker(tracker); + dsky.setProgError(prog); + + Log.d(TAG, String.format("[DSKY indicators] vel = %s, noAtt = %s, alt = %s, gimbalLock = %s, tracker = %s, prog = %s", + vel, noAtt, alt, gimbalLock, tracker, prog)); + break; + } + + default: + } + + break; + } + + case 011: { + // TAG indicator lights + final boolean compActy = (packet.getDataCode() & 0b0000_0010) != 0; + final boolean uplinkActy = (packet.getDataCode() & 0b0000_0100) != 0; + final boolean temp = (packet.getDataCode() & 0b0000_1000) != 0; + + dsky.setCompActy(compActy); + dsky.setUplinkActy(uplinkActy); + dsky.setTemp(temp); + + Log.d(TAG, String.format("[DSKY indicators] compActy = %s, uplinkActy = %s, temp = %s", + compActy, uplinkActy, temp)); + + break; + } + + default: + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java b/app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java new file mode 100755 index 0000000..adcb3a8 --- /dev/null +++ b/app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java @@ -0,0 +1,243 @@ +package de.rumpold.androiddsky.ui; + +import android.annotation.SuppressLint; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.IdRes; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import de.rumpold.androiddsky.DSKY; +import de.rumpold.androiddsky.R; + +/** + * An example full-screen activity that shows and hides the system UI (i.e. + * status bar and navigation/system bar) with user interaction. + */ +public class DSKYActivity extends AppCompatActivity { + /** + * Whether or not the system UI should be auto-hidden after + * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. + */ + private static final boolean AUTO_HIDE = true; + + /** + * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after + * user interaction before hiding the system UI. + */ + private static final int AUTO_HIDE_DELAY_MILLIS = 3000; + + /** + * Some older devices needs a small delay between UI widget updates + * and a change of the status and navigation bar. + */ + private static final int UI_ANIMATION_DELAY = 300; + + private final Handler mHideHandler = new Handler(); + + private View mContentView; + private final Runnable mHidePart2Runnable = new Runnable() { + @SuppressLint("InlinedApi") + @Override + public void run() { + // Delayed removal of status and navigation bar + + // Note that some of these constants are new as of API 16 (Jelly Bean) + // and API 19 (KitKat). It is safe to use them, as they are inlined + // at compile-time and do nothing on earlier devices. + mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + }; + private View mControlsView; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // Delayed display of UI elements + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + mControlsView.setVisibility(View.VISIBLE); + } + }; + private boolean mVisible; + private final Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hide(); + } + }; + + private final DSKY dsky = new DSKY(this); + + public void updateIndicator(@IdRes final int id, final boolean state) { + mHideHandler.post(new Runnable() { + @Override + public void run() { + TextView indicator = (TextView) findViewById(id); + if (indicator != null) { + if (state) { + final int color; + if ("right".equals(indicator.getTag())) { + color = getResources().getColor(R.color.indicatorRightActive); + } else { + color = getResources().getColor(R.color.indicatorActive); + } + + indicator.setBackgroundColor(color); + } else { + indicator.setBackgroundColor(getResources().getColor(R.color.indicatorPassive)); + } + } + } + }); + } + + public void updateDisplay(@IdRes final int id, final String value) { + mHideHandler.post(new Runnable() { + @Override + public void run() { + TextView display = (TextView) findViewById(id); + display.setText(value); + } + }); + } + + /** + * Touch listener to use for in-layout UI controls to delay hiding the + * system UI. This is to prevent the jarring behavior of controls going away + * while interacting with activity UI. + */ + private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (AUTO_HIDE) { + delayedHide(AUTO_HIDE_DELAY_MILLIS); + } + return false; + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_dsky); + + mVisible = true; + mControlsView = findViewById(R.id.controlBar); + mContentView = findViewById(R.id.contentView); + + + // Set up the user interaction to manually show or hide the system UI. + mContentView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + toggle(); + } + }); + + // Upon interacting with UI controls, delay any scheduled hide() + // operations to prevent the jarring behavior of controls going away + // while interacting with the UI. + + View.OnClickListener buttonListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + final Button button = (Button) view; + dsky.buttonPressed(button.getText().toString()); + } + }; + + findViewById(R.id.btn_verb).setOnClickListener(buttonListener); + findViewById(R.id.btn_noun).setOnClickListener(buttonListener); + findViewById(R.id.btn_plus).setOnClickListener(buttonListener); + findViewById(R.id.btn_minus).setOnClickListener(buttonListener); + findViewById(R.id.btn_0).setOnClickListener(buttonListener); + findViewById(R.id.btn_1).setOnClickListener(buttonListener); + findViewById(R.id.btn_2).setOnClickListener(buttonListener); + findViewById(R.id.btn_3).setOnClickListener(buttonListener); + findViewById(R.id.btn_4).setOnClickListener(buttonListener); + findViewById(R.id.btn_5).setOnClickListener(buttonListener); + findViewById(R.id.btn_6).setOnClickListener(buttonListener); + findViewById(R.id.btn_7).setOnClickListener(buttonListener); + findViewById(R.id.btn_8).setOnClickListener(buttonListener); + findViewById(R.id.btn_9).setOnClickListener(buttonListener); + findViewById(R.id.btn_clr).setOnClickListener(buttonListener); + findViewById(R.id.btn_pro).setOnClickListener(buttonListener); + findViewById(R.id.btn_key_rel).setOnClickListener(buttonListener); + findViewById(R.id.btn_entr).setOnClickListener(buttonListener); + findViewById(R.id.btn_rset).setOnClickListener(buttonListener); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + // Trigger the initial hide() shortly after the activity has been + // created, to briefly hint to the user that UI controls + // are available. + delayedHide(100); + + dsky.connect(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + dsky.disconnect(); + } + + private void toggle() { + if (mVisible) { + hide(); + } else { + show(); + } + } + + private void hide() { + // Hide UI first + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + mControlsView.setVisibility(View.GONE); + mVisible = false; + + // Schedule a runnable to remove the status and navigation bar after a delay + mHideHandler.removeCallbacks(mShowPart2Runnable); + mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + } + + @SuppressLint("InlinedApi") + private void show() { + // Show the system bar + mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + mVisible = true; + + // Schedule a runnable to display UI elements after a delay + mHideHandler.removeCallbacks(mHidePart2Runnable); + mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); + } + + /** + * Schedules a call to hide() in [delay] milliseconds, canceling any + * previously scheduled calls. + */ + private void delayedHide(int delayMillis) { + mHideHandler.removeCallbacks(mHideRunnable); + mHideHandler.postDelayed(mHideRunnable, delayMillis); + } +} diff --git a/app/src/main/res/layout/activity_dsky.xml b/app/src/main/res/layout/activity_dsky.xml new file mode 100755 index 0000000..574b328 --- /dev/null +++ b/app/src/main/res/layout/activity_dsky.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_7seg.xml b/app/src/main/res/layout/layout_7seg.xml new file mode 100755 index 0000000..6522e77 --- /dev/null +++ b/app/src/main/res/layout/layout_7seg.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_indicator_lights.xml b/app/src/main/res/layout/layout_indicator_lights.xml new file mode 100755 index 0000000..ff2eaa9 --- /dev/null +++ b/app/src/main/res/layout/layout_indicator_lights.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_keyboard.xml b/app/src/main/res/layout/layout_keyboard.xml new file mode 100755 index 0000000..0be5b1e --- /dev/null +++ b/app/src/main/res/layout/layout_keyboard.xml @@ -0,0 +1,189 @@ + + + + + +