├── LICENSE ├── app-debug.apk ├── app-release.apk ├── app ├── app.iml ├── build.gradle ├── lint.xml └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── mobi │ │ └── omegacentauri │ │ └── vectordisplay │ │ ├── BluetoothService.java │ │ ├── ConnectionService.java │ │ ├── Coords.java │ │ ├── DisplayState.java │ │ ├── IntCoords.java │ │ ├── MainActivity.java │ │ ├── Options.java │ │ ├── RecordAndPlay.java │ │ ├── Transformation.java │ │ ├── UsbService.java │ │ ├── VectorAPI.java │ │ ├── VectorView.java │ │ ├── WifiService.java │ │ └── commands │ │ ├── AddButton.java │ │ ├── Arc.java │ │ ├── Attribute16.java │ │ ├── Attribute32.java │ │ ├── Attribute8.java │ │ ├── Circle.java │ │ ├── Clear.java │ │ ├── Command.java │ │ ├── DeleteButton.java │ │ ├── DrawBitmap.java │ │ ├── FillCircle.java │ │ ├── FillPoly.java │ │ ├── FillRectangle.java │ │ ├── FillTriangle.java │ │ ├── Initialize.java │ │ ├── InitializeWithResolution.java │ │ ├── Line.java │ │ ├── Point.java │ │ ├── PolyLine.java │ │ ├── PopupMessage.java │ │ ├── Reset.java │ │ ├── RoundedRectangle.java │ │ ├── Text.java │ │ └── Update.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── menu │ └── menu.xml │ ├── mipmap │ └── icon.png │ └── values │ ├── arrays.xml │ ├── colors.xml │ └── strings.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── icon.svg ├── import-summary.txt ├── local.properties ├── settings.gradle ├── test ├── arc.py ├── bitmap.py ├── bitmap_data.py ├── helper.py ├── nettest.py ├── poly.py ├── rects.py ├── roundedrects.py ├── serialtest.py ├── text.py └── vectordisplay.py ├── usbserial-4.5.2-release ├── build.gradle ├── usbserial-4.5.2-release.aar └── usbserial-4.5.2-release.iml └── vectordisplay.iml /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | VectorDisplay Copyright (c) 2018 Omega Centauri Software 4 | UsbSerial library Copyright (c) 2014 Felipe Herranz 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpruss/VectorDisplay/286bf571c13b825e34b3911e5875d997b8519454/app-debug.apk -------------------------------------------------------------------------------- /app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpruss/VectorDisplay/286bf571c13b825e34b3911e5875d997b8519454/app-release.apk -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | android { 3 | compileSdkVersion 23 4 | buildToolsVersion "26.0.2" 5 | defaultConfig { 6 | applicationId "mobi.omegacentauri.vectordisplay" 7 | minSdkVersion 12 8 | targetSdkVersion 23 9 | versionCode 18 10 | versionName "0.38.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 16 | } 17 | } 18 | lintOptions { 19 | checkReleaseBuilds false 20 | // Or, if you prefer, you can continue to check for errors in release builds, 21 | // but continue the build even when errors are found: 22 | abortOnError false 23 | } 24 | productFlavors { 25 | } 26 | } 27 | 28 | dependencies { 29 | compile project(':usbserial-4.5.2-release') 30 | compile 'com.android.support:appcompat-v7:23.3.0' 31 | } -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 38 | 39 | 42 | 43 | 44 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/BluetoothService.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.bluetooth.BluetoothSocket; 6 | import android.content.BroadcastReceiver; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.os.Build; 11 | import android.util.Log; 12 | 13 | import com.felhr.usbserial.UsbSerialInterface; 14 | 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.util.UUID; 19 | 20 | public class BluetoothService extends ConnectionService { 21 | public static final String EXTRA_DEVICE_ADDRESS = "deviceAddress"; 22 | public static boolean SERVICE_CONNECTED = false; 23 | String btAddress = null; 24 | BluetoothDevice device = null; 25 | BluetoothSocket mSock = null; 26 | public static final String ACTION_BLUETOOTH_DEVICE_SELECTED="mobi.omegacentauri.vectordisplay.BLUETOOTH_DEVICE_SELECTED"; 27 | 28 | private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { 29 | @Override 30 | public void onReceive(Context arg0, Intent arg1) { 31 | if (arg1.getAction().equals(ACTION_BLUETOOTH_DEVICE_SELECTED)) { 32 | btAddress = arg1.getExtras().getString(EXTRA_DEVICE_ADDRESS); 33 | new ConnectionThread().start(); 34 | } 35 | } 36 | }; 37 | /* 38 | * Data received from serial port will be received here. 39 | */ 40 | private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { 41 | @Override 42 | public void onReceivedData(byte[] data) { 43 | feed(data); 44 | } 45 | }; 46 | private OutputStream out; 47 | private InputStream in; 48 | 49 | private void setFilter() { 50 | IntentFilter filter = new IntentFilter(); 51 | filter.addAction(ACTION_BLUETOOTH_DEVICE_SELECTED); 52 | registerReceiver(usbReceiver, filter); 53 | } 54 | @Override 55 | synchronized public void onDestroy() { 56 | super.onDestroy(); 57 | stopSocket(); 58 | unregisterReceiver(usbReceiver); 59 | BluetoothService.SERVICE_CONNECTED = false; 60 | } 61 | 62 | 63 | @Override 64 | public void onCreate() { 65 | super.onCreate(); 66 | btAddress = null; 67 | device = null; 68 | mSock = null; 69 | Log.v("VectorDisplay", "on create BluetoothService"); 70 | BluetoothService.SERVICE_CONNECTED = true; 71 | setFilter(); 72 | } 73 | 74 | @Override 75 | public void disconnectDevice() { 76 | stopSocket(); 77 | } 78 | 79 | @Override 80 | synchronized public void close() { 81 | stopSocket(); 82 | stopSelf(); 83 | } 84 | 85 | @Override 86 | public void setRecord(RecordAndPlay r) { 87 | super.setRecord(r); 88 | } 89 | 90 | public void stopSocket() { 91 | if (mSock != null) 92 | broadcast(ACTION_DEVICE_DISCONNECTED); 93 | if (mSock != null) { 94 | try { 95 | mSock.close(); 96 | } catch (IOException e1) { 97 | } 98 | mSock = null; 99 | } 100 | } 101 | 102 | @Override 103 | synchronized public void write(byte[] data) { 104 | if (out != null) { 105 | try { 106 | out.write(data); 107 | } catch (IOException e) { 108 | } 109 | } 110 | } 111 | 112 | /* 113 | * A simple thread to open a serial port. 114 | * Although it should be a fast operation. moving usb operations away from UI thread is a good thing. 115 | */ 116 | private class ConnectionThread extends Thread { 117 | @Override 118 | public void run() { 119 | byte[] byteBuffer = new byte[256]; 120 | 121 | BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); 122 | device = null; 123 | for (BluetoothDevice d : btAdapter.getBondedDevices()) 124 | if (d.getAddress().equals(btAddress)) { 125 | device = d; 126 | break; 127 | } 128 | 129 | if (device == null) { 130 | broadcast(ACTION_DEVICE_UNSUPPORTED); 131 | return; 132 | } 133 | 134 | mSock = null; 135 | try { 136 | mSock = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); 137 | } catch (IOException e) { 138 | broadcast(ACTION_DEVICE_UNSUPPORTED); 139 | return; 140 | } 141 | 142 | try { 143 | BluetoothSocket sock = mSock; // local copy 144 | sock.connect(); 145 | out = sock.getOutputStream(); 146 | in = sock.getInputStream(); 147 | broadcast(ConnectionService.ACTION_DEVICE_CONNECTED); 148 | 149 | while(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH || sock.isConnected()) { 150 | int n = in.read(byteBuffer); 151 | record.feed(byteBuffer, n); 152 | } 153 | } catch (IOException e) { 154 | stopSocket(); 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/ConnectionService.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.app.Service; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.hardware.usb.UsbManager; 7 | import android.os.Binder; 8 | import android.os.IBinder; 9 | import android.util.Log; 10 | 11 | abstract class ConnectionService extends Service { 12 | public static final String ACTION_DEVICE_CONNECTED = "mobi.omegacentauri.vectordisplay.CONNECTED"; 13 | public static final String ACTION_DEVICE_DISCONNECTED = "mobi.omegacentauri.vectordisplay.DISCONNECTED"; 14 | public static final String ACTION_DEVICE_UNSUPPORTED = "mobi.omegacentauri.vectordisplay.UNSUPPORTED"; 15 | protected IBinder binder = new ConnectionBinder(); 16 | protected RecordAndPlay record; 17 | protected Context context; 18 | 19 | @Override 20 | public IBinder onBind(Intent intent) { 21 | return binder; 22 | } 23 | 24 | @Override 25 | public int onStartCommand(Intent intent, int flags, int startId) { 26 | return Service.START_NOT_STICKY; 27 | } 28 | 29 | protected void feed(byte[] data) { 30 | if (record != null) 31 | record.feed(data); 32 | } 33 | 34 | public class ConnectionBinder extends Binder { 35 | public ConnectionService getService() { 36 | return ConnectionService.this; 37 | } 38 | } 39 | 40 | public void broadcast(String msg) { 41 | Intent intent = new Intent(msg); 42 | Log.v("VectorDisplay", "sending "+intent.getAction()); 43 | sendBroadcast(intent); 44 | } 45 | 46 | public void setRecord(RecordAndPlay r) { 47 | this.record = r; 48 | } 49 | 50 | abstract public void write(byte[] data); 51 | /* 52 | * onCreate will be executed when service is started. It configures an IntentFilter to listen for 53 | * incoming Intents (USB ATTACHED, USB DETACHED...) and it tries to open a serial port. 54 | */ 55 | 56 | public void disconnectDevice() { 57 | broadcast(ACTION_DEVICE_DISCONNECTED); 58 | } 59 | 60 | abstract public void close(); 61 | 62 | @Override 63 | public void onCreate() { 64 | this.context = this; 65 | MainActivity.log("service created "+this.getClass()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/Coords.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | public class Coords { 4 | public float x; 5 | public float y; 6 | 7 | public Coords(float x, float y) { 8 | this.x = x; 9 | this.y = y; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/DisplayState.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.Typeface; 7 | import android.text.TextPaint; 8 | 9 | public class DisplayState implements Cloneable { 10 | public float cursorX; 11 | public float cursorY; 12 | public boolean wrap; 13 | public int width; 14 | public int height; 15 | public float pixelAspectRatio; 16 | public int foreColor; 17 | public int backColor; 18 | public float thickness; 19 | public float textSize; 20 | public char hAlignText; 21 | public char vAlignText; 22 | public boolean opaqueTextBackground; 23 | public boolean continuousUpdate; 24 | public static final char ALIGN_LEFT = 'l'; 25 | public static final char ALIGN_RIGHT = 'r'; 26 | public static final char ALIGN_CENTER = 'c'; 27 | public static final char ALIGN_TOP = 't'; 28 | public static final char ALIGN_BOTTOM = 'b'; 29 | public static final char ALIGN_BASELINE = 'l'; 30 | public int textBackColor; 31 | public int textForeColor; 32 | public byte rotate; 33 | public float monoFontScaleX; 34 | public float monoFontScale; 35 | public boolean cp437; 36 | public boolean rounded; 37 | public byte fontInfo; 38 | public static final byte FONT_TYPEFACE_MASK = 0x7; 39 | public static final byte FONT_SERIF = 0; 40 | public static final byte FONT_SANS = 1; 41 | public static final byte FONT_MONO = 2; 42 | public static final byte FONT_ITALIC = 8; 43 | public static final byte FONT_BOLD = 16; 44 | public static final byte FONTINFO_MASK = 0x1F; 45 | public static TextPaint[] fontPaints = new TextPaint[FONTINFO_MASK+1]; 46 | 47 | public Object clone() throws 48 | CloneNotSupportedException 49 | { 50 | return super.clone(); 51 | } 52 | 53 | public void reset() { 54 | width = 240; 55 | height = 320; 56 | pixelAspectRatio = 1.0f; 57 | foreColor = Color.WHITE; 58 | backColor = Color.BLACK; 59 | textBackColor = Color.BLACK; 60 | textForeColor = Color.WHITE; 61 | thickness = 1f; 62 | textSize = 8; 63 | hAlignText = 'l'; 64 | vAlignText = 't'; 65 | fontInfo = FONT_MONO; 66 | rotate = 0; 67 | cp437 = false; 68 | opaqueTextBackground = true; 69 | continuousUpdate = true; 70 | rounded = true; 71 | measureMonoFont(); 72 | wrap = true; 73 | cursorX = 0f; 74 | cursorY = 0f; 75 | } 76 | 77 | static public TextPaint getTextPaint(byte fontInfo) { 78 | int f = fontInfo & FONTINFO_MASK; 79 | 80 | TextPaint p = fontPaints[f]; 81 | 82 | if (p == null) { 83 | p = new TextPaint(); 84 | p.setStyle(Paint.Style.FILL); 85 | Typeface family; 86 | switch (f & FONT_TYPEFACE_MASK) { 87 | case FONT_SANS: 88 | family = Typeface.SANS_SERIF; 89 | break; 90 | case FONT_MONO: 91 | family = Typeface.MONOSPACE; 92 | break; 93 | default: 94 | family = Typeface.SERIF; 95 | break; 96 | } 97 | 98 | int style; 99 | 100 | switch (f & (FONT_BOLD | FONT_ITALIC)) { 101 | case FONT_BOLD: 102 | style = Typeface.BOLD; 103 | break; 104 | case FONT_ITALIC: 105 | style = Typeface.ITALIC; 106 | break; 107 | case FONT_BOLD | FONT_ITALIC: 108 | style = Typeface.BOLD_ITALIC; 109 | break; 110 | default: 111 | style = Typeface.NORMAL; 112 | break; 113 | } 114 | 115 | p.setTypeface(Typeface.create(family, style)); 116 | fontPaints[f] = p; 117 | } 118 | 119 | return p; 120 | } 121 | 122 | public TextPaint getTextPaint() { 123 | return getTextPaint(fontInfo); 124 | } 125 | 126 | private void measureMonoFont() { 127 | TextPaint p = new TextPaint(); 128 | p.setTypeface(Typeface.MONOSPACE); 129 | p.setTextSize(8f); 130 | float h = p.getFontMetrics().bottom - p.getFontMetrics().top; 131 | monoFontScale = 8f / h; 132 | p.setTextSize(monoFontScale * 8f); 133 | float w = p.measureText("0123456789")/10f; 134 | monoFontScaleX = 5f / w; 135 | } 136 | 137 | public DisplayState() { 138 | reset(); 139 | } 140 | 141 | public float getAspectRatio() { 142 | return width*pixelAspectRatio/height; 143 | } 144 | 145 | public int rotatedWidth() { 146 | return rotate % 2 == 0 ? width : height; 147 | } 148 | 149 | public int rotatedHeight() { 150 | return rotate % 2 == 0 ? height : width; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/IntCoords.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | public class IntCoords { 4 | public int x; 5 | public int y; 6 | 7 | public IntCoords(int x, int y) { 8 | this.x = x; 9 | this.y = y; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/MainActivity.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.content.BroadcastReceiver; 6 | import android.content.ComponentName; 7 | import android.content.Context; 8 | import android.content.DialogInterface; 9 | import android.content.Intent; 10 | import android.content.IntentFilter; 11 | import android.content.ServiceConnection; 12 | import android.content.SharedPreferences; 13 | import android.content.pm.ActivityInfo; 14 | import android.content.res.Configuration; 15 | import android.net.Uri; 16 | import android.os.Bundle; 17 | import android.os.Handler; 18 | import android.os.IBinder; 19 | import android.os.Message; 20 | import android.preference.PreferenceManager; 21 | import android.support.v7.app.AlertDialog; 22 | import android.support.v7.app.AppCompatActivity; 23 | import android.text.Html; 24 | import android.util.Log; 25 | import android.view.Menu; 26 | import android.view.MenuInflater; 27 | import android.view.MenuItem; 28 | import android.view.View; 29 | import android.widget.AdapterView; 30 | import android.widget.ArrayAdapter; 31 | import android.widget.EditText; 32 | import android.widget.ListView; 33 | import android.widget.TextView; 34 | import android.widget.Toast; 35 | 36 | import java.lang.ref.WeakReference; 37 | import java.util.ArrayList; 38 | import java.util.Collections; 39 | import java.util.Comparator; 40 | import java.util.List; 41 | import java.util.Set; 42 | 43 | import mobi.omegacentauri.vectordisplay.commands.Clear; 44 | import mobi.omegacentauri.vectordisplay.commands.Reset; 45 | 46 | public class MainActivity extends AppCompatActivity { 47 | public static final String KEY_COMMAND = "cmd"; 48 | public static final String KEY_LABEL = "label"; 49 | public static final String KEY_ASPECT = "aspect"; 50 | public int physicalWidth = -1; 51 | public int physicalHeight = -1; 52 | public RecordAndPlay record; 53 | SharedPreferences prefs; 54 | List userCommands; 55 | List userLabels; 56 | ArrayAdapter commandListAdapter = null; 57 | static final boolean DEBUG = false; 58 | ListView commandList; 59 | MyHandler commandHandler; 60 | public static final int ADD_COMMAND = 1; 61 | public static final int DELETE_COMMAND = 2; 62 | public static final int DELETE_ALL_COMMANDS = 3; 63 | public static final int ACK = 4; 64 | public static final int RESET_VIEW = 5; 65 | public static final int INVALIDATE_VIEW = 6; 66 | public static final int TOAST = 7; 67 | boolean attached; 68 | byte[] outBuf = new byte[8]; 69 | 70 | static public void log(String s) { 71 | if (DEBUG) 72 | Log.v("VectorDisplay", s); 73 | } 74 | 75 | /* 76 | * Notifications from UsbService will be received here. 77 | */ 78 | private final BroadcastReceiver mConnectionReceiver = new BroadcastReceiver() { 79 | @Override 80 | public void onReceive(Context context, Intent intent) { 81 | switch (intent.getAction()) { 82 | case ConnectionService.ACTION_DEVICE_CONNECTED: 83 | Toast.makeText(context, "Device ready", Toast.LENGTH_SHORT).show(); 84 | // if (prefs.getBoolean(Options.PREF_RESET_ON_CONNECT, true)) 85 | // record.feed(new Reset(record.parser.state)); 86 | attached = true; 87 | break; 88 | case UsbService.ACTION_DEVICE_PERMISSION_NOT_GRANTED: // USB PERMISSION NOT GRANTED 89 | Toast.makeText(context, "Device permission not granted", Toast.LENGTH_SHORT).show(); 90 | attached = false; 91 | break; 92 | case UsbService.ACTION_NO_USB: // NO USB CONNECTED 93 | Toast.makeText(context, "No USB device connected", Toast.LENGTH_SHORT).show(); 94 | attached = false; 95 | break; 96 | case ConnectionService.ACTION_DEVICE_UNSUPPORTED: // USB DISCONNECTED 97 | Toast.makeText(context, "Device not supported", Toast.LENGTH_SHORT).show(); 98 | attached = false; 99 | break; 100 | case ConnectionService.ACTION_DEVICE_DISCONNECTED: // USB DISCONNECTED 101 | Toast.makeText(context, "Device disconnected", Toast.LENGTH_SHORT).show(); 102 | attached = false; 103 | break; 104 | case UsbService.ACTION_USB_NOT_SUPPORTED: // USB NOT SUPPORTED 105 | Toast.makeText(context, "USB device not supported", Toast.LENGTH_SHORT).show(); 106 | attached = false; 107 | break; 108 | default: 109 | return; 110 | } 111 | if (record != null) { 112 | record.setConnected(attached); 113 | record.forceUpdate(); 114 | } 115 | supportInvalidateOptionsMenu(); 116 | } 117 | }; 118 | public ConnectionService connectionService; 119 | private TextView display; 120 | private EditText editText; 121 | private final ServiceConnection connection = new ServiceConnection() { 122 | @Override 123 | public void onServiceConnected(ComponentName arg0, IBinder arg1) { 124 | synchronized (MainActivity.this) { 125 | connectionService = ((ConnectionService.ConnectionBinder) arg1).getService(); 126 | Log.v("VectorDisplay", "setRecord"); 127 | connectionService.setRecord(record); 128 | } 129 | } 130 | 131 | @Override 132 | public void onServiceDisconnected(ComponentName arg0) { 133 | synchronized (MainActivity.this) { 134 | connectionService = null; 135 | } 136 | } 137 | }; 138 | 139 | private int connectionMode() { 140 | return prefs.getInt(Options.PREF_CONNECTION, Options.OPT_USB); 141 | } 142 | 143 | private void chooseConnection() { 144 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 145 | CharSequence[] array = getResources().getStringArray(R.array.modes); 146 | final int oldConn = connectionMode(); 147 | builder.setTitle("Select Connection") 148 | .setSingleChoiceItems(array, oldConn, new DialogInterface.OnClickListener() { 149 | 150 | @Override 151 | public void onClick(DialogInterface dialog, int which) { 152 | if (which == oldConn) 153 | return; 154 | prefs.edit().putInt(Options.PREF_CONNECTION, which).commit(); 155 | if (connectionService != null) 156 | connectionService.close(); 157 | disconnectService(); 158 | connectService(); 159 | supportInvalidateOptionsMenu(); 160 | dialog.dismiss(); 161 | } 162 | }); 163 | builder.create().show(); 164 | } 165 | 166 | private void chooseFPS() { 167 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 168 | final CharSequence[] array = getResources().getStringArray(R.array.fpss); 169 | final int fps = prefs.getInt(Options.PREF_FPS, 30); 170 | int selected = -1; 171 | for (int i=0;i devs = new ArrayList(); 197 | devs.addAll(btAdapter.getBondedDevices()); 198 | if (devs == null) { 199 | Toast.makeText(this, "No paired Bluetooth devices", Toast.LENGTH_LONG).show(); 200 | return; 201 | } 202 | Collections.sort(devs, new Comparator(){ 203 | @Override 204 | public int compare(BluetoothDevice lhs, BluetoothDevice rhs) { 205 | return String.CASE_INSENSITIVE_ORDER.compare(lhs.getName(), rhs.getName()); 206 | }}); 207 | int selected = -1; 208 | String lastAddress = prefs.getString(Options.PREF_LAST_BLUETOOTH_ADDRESS, ""); 209 | CharSequence[] devLabels = new CharSequence[devs.size()]; 210 | for (int i=0; i(); 251 | userLabels = new ArrayList(); 252 | 253 | commandHandler = new MyHandler(this); 254 | record = new RecordAndPlay(this, commandHandler); 255 | 256 | setContentView(R.layout.activity_main); 257 | 258 | commandList = (ListView) findViewById(R.id.commands); 259 | commandListAdapter = new ArrayAdapter(this, 260 | android.R.layout.simple_list_item_1, 261 | android.R.id.text1, 262 | userLabels); 263 | commandList.setAdapter(commandListAdapter); 264 | commandList.setOnItemClickListener(new AdapterView.OnItemClickListener() { 265 | @Override 266 | public void onItemClick(AdapterView parent, View view, int position, long id) { 267 | outBuf[0] = 'B'; 268 | outBuf[1] = 'T'; 269 | outBuf[2] = userCommands.get(position); 270 | for (int i = 3; i < 8; i++) 271 | outBuf[i] = 0; 272 | synchronized (MainActivity.this) { 273 | if (connectionService != null) 274 | connectionService.write(outBuf); 275 | } 276 | } 277 | }); 278 | 279 | setOrientation(); 280 | // record = new RecordAndPlay(this, this); 281 | resetVectorView(this, record.parser.state.getAspectRatio()); 282 | 283 | MainActivity.log("OnCreate"); 284 | } 285 | 286 | private void stopServices() { 287 | stopService(new Intent(this, UsbService.class)); 288 | stopService(new Intent(this, WifiService.class)); 289 | stopService(new Intent(this, BluetoothService.class)); 290 | } 291 | 292 | static void resetVectorView(MainActivity main, float aspectRatio) { 293 | MainActivity.log("reset to aspect "+aspectRatio); 294 | VectorView v = (VectorView) main.findViewById(R.id.vector); 295 | if (v != null) { 296 | v.aspectRatio = aspectRatio; 297 | v.getParent().requestLayout(); 298 | v.forceLayout(); 299 | } 300 | } 301 | 302 | void updateFPS() { 303 | record.updateTimeMillis = 1000 / (long) prefs.getInt(Options.PREF_FPS, 30); 304 | } 305 | 306 | @Override 307 | public void onResume() { 308 | super.onResume(); 309 | updateFPS(); 310 | connectService(); 311 | } 312 | 313 | private void connectService() { 314 | Log.v("VectorDisplay", "connectService()"); 315 | setFilters(); // Start listening notifications from UsbService 316 | switch (connectionMode()) { 317 | case Options.OPT_IP: 318 | Log.v("VectorDisplay", "starting wifi service"); 319 | startService(WifiService.class, connection, null); // Start WifiService (if it was not started before) and Bind it 320 | record.setDisconnectedStatus(new String[]{"WiFi device not connected"}); 321 | break; 322 | case Options.OPT_BLUETOOTH: 323 | Log.v("VectorDisplay", "starting bluetooth service"); 324 | startService(BluetoothService.class, connection, null); // Start WifiService (if it was not started before) and Bind it 325 | record.setDisconnectedStatus(new String[]{"Start Bluetooth device and press CONNECT"}); 326 | break; 327 | default: 328 | startService(UsbService.class, connection, null); // Start UsbService(if it was not started before) and Bind it 329 | record.setDisconnectedStatus(new String[]{"USB Disconnected"}); 330 | break; 331 | } 332 | VectorView view = (VectorView)findViewById(R.id.vector); 333 | /* if (view != null) { 334 | view.invalidate(); 335 | } */ 336 | } 337 | 338 | @Override 339 | public void onPause() { 340 | super.onPause(); 341 | disconnectService(); 342 | } 343 | 344 | @Override 345 | public void onStop() { 346 | super.onStop(); 347 | stopServices(); 348 | } 349 | 350 | void disconnectService() { 351 | unregisterReceiver(mConnectionReceiver); 352 | try { 353 | unbindService(connection); 354 | } catch(Exception e) {} 355 | } 356 | 357 | private void startService(Class service, ServiceConnection serviceConnection, Bundle extras) { 358 | if (!UsbService.SERVICE_CONNECTED) { 359 | Intent startService = new Intent(this, service); 360 | if (extras != null && !extras.isEmpty()) { 361 | Set keys = extras.keySet(); 362 | for (String key : keys) { 363 | String extra = extras.getString(key); 364 | startService.putExtra(key, extra); 365 | } 366 | } 367 | startService(startService); 368 | } 369 | Intent bindingIntent = new Intent(this, service); 370 | bindService(bindingIntent, serviceConnection, Context.BIND_AUTO_CREATE); 371 | } 372 | 373 | private void setFilters() { 374 | IntentFilter filter = new IntentFilter(); 375 | filter.addAction(ConnectionService.ACTION_DEVICE_CONNECTED); 376 | filter.addAction(UsbService.ACTION_NO_USB); 377 | filter.addAction(ConnectionService.ACTION_DEVICE_DISCONNECTED); 378 | filter.addAction(ConnectionService.ACTION_DEVICE_UNSUPPORTED); 379 | filter.addAction(UsbService.ACTION_USB_NOT_SUPPORTED); 380 | filter.addAction(UsbService.ACTION_DEVICE_PERMISSION_NOT_GRANTED); 381 | registerReceiver(mConnectionReceiver, filter); 382 | } 383 | 384 | @Override 385 | public boolean onCreateOptionsMenu(Menu menu) { 386 | int conn = connectionMode(); 387 | MenuInflater inflater = getMenuInflater(); 388 | inflater.inflate(R.menu.menu, menu); 389 | MenuItem item = menu.findItem(R.id.mode); 390 | item.setTitle(getResources().getStringArray(R.array.modes)[conn]); 391 | item = menu.findItem(R.id.disconnect); 392 | item.setVisible(attached); 393 | item = menu.findItem(R.id.connect); 394 | item.setVisible(!attached && conn == Options.OPT_BLUETOOTH); 395 | 396 | return super.onCreateOptionsMenu(menu); 397 | } 398 | 399 | @Override 400 | public boolean onOptionsItemSelected(MenuItem item) { 401 | int id = item.getItemId(); 402 | if (id == R.id.clear) { 403 | record.feed(new Clear(record.parser.state)); 404 | } 405 | else if (id == R.id.reset) { 406 | record.feed(new Reset(record.parser.state)); 407 | } 408 | else if (id == R.id.rotate) { 409 | boolean landscape = ! prefs.getBoolean(Options.PREF_LANDSCAPE, false); 410 | prefs.edit().putBoolean(Options.PREF_LANDSCAPE, landscape).commit(); 411 | setOrientation(); 412 | } 413 | else if (id == R.id.fps) { 414 | chooseFPS(); 415 | } 416 | else if (id == R.id.license) { 417 | message("The MIT License", 418 | "VectorDisplay Copyright (c) 2018 Omega Centauri Software
" + 419 | "UsbSerial library Copyright (c) 2014 Felipe Herranz

" + 420 | "Permission is hereby granted, free of charge, to any person obtaining a copy\n" + 421 | "of this software and associated documentation files (the \"Software\"), to deal\n" + 422 | "in the Software without restriction, including without limitation the rights\n" + 423 | "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n" + 424 | "copies of the Software, and to permit persons to whom the Software is\n" + 425 | "furnished to do so, subject to the following conditions:
\n" + 426 | "\n" + 427 | "The above copyright notice and this permission notice shall be included in all\n" + 428 | "copies or substantial portions of the Software.
\n" + 429 | "\n" + 430 | "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" + 431 | "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" + 432 | "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n" + 433 | "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n" + 434 | "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n" + 435 | "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n" + 436 | "SOFTWARE.", false); 437 | } 438 | else if (id == R.id.help) { 439 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.instructables.com/id/TabletPhone-As-Arduino-Screen-and-a-2-Oscilloscope/")); 440 | startActivity(browserIntent); 441 | } 442 | else if (id == R.id.mode) { 443 | chooseConnection(); 444 | } 445 | else if (id == R.id.disconnect) { 446 | if (connectionService != null) 447 | connectionService.disconnectDevice(); 448 | } 449 | else if (id == R.id.connect && connectionMode() == Options.OPT_BLUETOOTH) { 450 | chooseBluetoothDevice(); 451 | } 452 | return super.onOptionsItemSelected(item); 453 | } 454 | 455 | private void message(String title, String msg, final boolean exit) { 456 | AlertDialog alertDialog = new AlertDialog.Builder(this).create(); 457 | 458 | alertDialog.setTitle(title); 459 | alertDialog.setMessage(Html.fromHtml(msg)); 460 | alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, 461 | "OK", 462 | new DialogInterface.OnClickListener() { 463 | public void onClick(DialogInterface dialog, int which) { 464 | if(exit) finish(); 465 | } }); 466 | alertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 467 | public void onCancel(DialogInterface dialog) {if(exit) finish();} }); 468 | alertDialog.show(); 469 | } 470 | 471 | public static void sendResetViewMessage(Handler h, DisplayState state) { 472 | Message msg = h.obtainMessage(RESET_VIEW); 473 | Bundle b = new Bundle(); 474 | b.putFloat(KEY_ASPECT, state.getAspectRatio()); 475 | msg.setData(b); 476 | h.sendMessage(msg); 477 | } 478 | 479 | private static class MyHandler extends Handler { 480 | private final WeakReference mActivity; 481 | 482 | public MyHandler(MainActivity activity) { 483 | super(); 484 | mActivity = new WeakReference<>(activity); 485 | } 486 | 487 | @Override 488 | public void handleMessage(Message msg) { 489 | final MainActivity main = mActivity.get(); 490 | 491 | if (main == null || main.commandListAdapter == null) 492 | return; 493 | 494 | if (msg.what == MainActivity.DELETE_ALL_COMMANDS) { 495 | main.userCommands.clear(); 496 | main.commandListAdapter.clear(); 497 | main.commandList.setVisibility(main.userCommands.size() > 0 ? View.VISIBLE : View.GONE); 498 | } 499 | else if (msg.what == MainActivity.ADD_COMMAND || msg.what == MainActivity.DELETE_COMMAND) { 500 | byte cmd = msg.getData().getByte(MainActivity.KEY_COMMAND); 501 | for (int i=0; i 0 ? View.VISIBLE : View.GONE); 512 | MainActivity.resetVectorView(main, main.record.parser.state.getAspectRatio()); 513 | } 514 | else if (msg.what == MainActivity.ACK) { 515 | byte[] out = "Acknwld_".getBytes(); 516 | out[7] = (byte)msg.arg1; 517 | synchronized(main) { 518 | if (main.connectionService != null) 519 | main.connectionService.write(out); 520 | } 521 | } 522 | else if (msg.what == MainActivity.RESET_VIEW) { 523 | MainActivity.log("resetting view"); 524 | MainActivity.resetVectorView(main, msg.getData().getFloat(MainActivity.KEY_ASPECT)); 525 | } 526 | else if (msg.what == MainActivity.INVALIDATE_VIEW) { 527 | main.record.forceUpdate(); 528 | } 529 | else if (msg.what == MainActivity.TOAST) { 530 | String text = msg.getData().getString(MainActivity.KEY_LABEL); 531 | Log.v("VectorDisplay", "toast "+text); 532 | Toast.makeText(main, text, Toast.LENGTH_LONG).show(); 533 | } 534 | } 535 | } 536 | } 537 | 538 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/Options.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.content.SharedPreferences; 4 | import android.content.pm.ActivityInfo; 5 | import android.os.Bundle; 6 | import android.preference.PreferenceActivity; 7 | import android.preference.PreferenceManager; 8 | 9 | public class Options { 10 | public static final String PREF_LANDSCAPE = "landscape"; 11 | public static final String PREF_RESET_ON_CONNECT = "resetOnConnect"; 12 | public static final String PREF_FPS = "fps_int"; 13 | public static final String PREF_CONNECTION = "connection"; 14 | public static final int OPT_USB = 0; 15 | public static final int OPT_IP = 1; 16 | public static final int OPT_BLUETOOTH = 2; 17 | public static final int NUM_CONNECTION_OPTS = 3; 18 | public static final String PREF_LAST_BLUETOOTH_ADDRESS = "lastBT"; 19 | /* 20 | @Override 21 | public void onCreate(Bundle icicle) { 22 | super.onCreate(icicle); 23 | 24 | addPreferencesFromResource(R.xml.options); 25 | // 26 | // Preference p = getPreferenceScreen().findPreference(PREF_VOLUME); 27 | // if (p != null) { 28 | // SpeakerBoost.log("Setting PREF_VOLUME to "+defaultShowVolume()); 29 | // p.setDefaultValue(defaultShowVolume()); 30 | // } 31 | } 32 | 33 | @Override 34 | public void onResume() { 35 | super.onResume(); 36 | 37 | setRequestedOrientation( 38 | PreferenceManager.getDefaultSharedPreferences(this).getBoolean(Options.PREF_LANDSCAPE, false) ? 39 | ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 40 | 41 | } 42 | 43 | @Override 44 | public void onStop() { 45 | super.onStop(); 46 | } 47 | */ 48 | } -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/RecordAndPlay.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Canvas; 5 | import android.graphics.Matrix; 6 | import android.os.Handler; 7 | 8 | import mobi.omegacentauri.vectordisplay.commands.Command; 9 | import mobi.omegacentauri.vectordisplay.commands.Update; 10 | 11 | /** 12 | * Created by Alexander_Pruss on 10/13/2017. 13 | */ 14 | 15 | public class RecordAndPlay { 16 | private static final int MAX_ITEMS = 5000; 17 | private Command commands[] = new Command[MAX_ITEMS]; 18 | public VectorAPI parser; 19 | private int head; 20 | private int tail; 21 | Activity context; 22 | boolean continuous = false; 23 | volatile long waitTime = -1; 24 | public static final int LAYOUT_DELAY = 250; 25 | Handler commandHandler; 26 | Transformation curMatrix = new Transformation(); 27 | boolean connected = false; 28 | volatile boolean forcedUpdate = false; 29 | String[] disconnectedStatus = new String[] { "Disconnected" }; 30 | volatile public long updateTimeMillis; 31 | // TODO: control update speed 32 | // TODO: delay rendering after view change 33 | 34 | public RecordAndPlay(Activity c, Handler h) { 35 | head = 0; 36 | tail = 0; 37 | parser = new VectorAPI(c); 38 | context = c; 39 | commandHandler = h; 40 | } 41 | 42 | synchronized public void feed(Command c) { 43 | if (MainActivity.DEBUG) 44 | MainActivity.log("Feeding "+c.getClass()); 45 | if (c.handleCommand(commandHandler)) { 46 | waitTime = System.currentTimeMillis() + LAYOUT_DELAY; // TODO: fix this hack 47 | // so we correctly wait for the screen layout 48 | } 49 | 50 | continuous = c.state.continuousUpdate; 51 | 52 | if (c.needToClearHistory()) { 53 | head = tail; 54 | } 55 | 56 | if (c instanceof Update) { 57 | forceUpdate(); 58 | } 59 | 60 | if (!c.doesDraw()) 61 | return; 62 | 63 | commands[tail] = c; 64 | tail = (tail + 1) % MAX_ITEMS; 65 | if (tail == head) 66 | head = (head + 1) % MAX_ITEMS; 67 | } 68 | 69 | synchronized public void setConnected(boolean c) { 70 | connected = c; 71 | } 72 | 73 | public void setDisconnectedStatus(String[] lines) { 74 | disconnectedStatus = lines; 75 | } 76 | 77 | public void feed(byte[] data) { 78 | feed(data, data.length); 79 | } 80 | 81 | synchronized public void feed(byte[] data, int n) { 82 | for(int i=0; i= System.currentTimeMillis() || head == tail) 91 | return -1; 92 | if (continuous) 93 | return tail; 94 | int t0 = tail - 1; 95 | if (t0 < 0) 96 | t0 += MAX_ITEMS; 97 | while (t0 != head) { 98 | if (commands[t0] instanceof Update) { 99 | return (t0 + 1) % MAX_ITEMS; 100 | } 101 | t0--; 102 | if (t0<0) 103 | t0 = MAX_ITEMS - 1; 104 | } 105 | return -1; 106 | } 107 | 108 | synchronized public void draw(Canvas canvas) { 109 | MainActivity.log("drawing"); 110 | forcedUpdate = false; 111 | 112 | int endPos = -1; 113 | 114 | endPos = findAdjustedTail(); 115 | 116 | if (endPos < 0) 117 | return; 118 | 119 | while (head != endPos) { 120 | try { 121 | Command c = commands[head]; 122 | synchronized (curMatrix) { 123 | curMatrix.update(canvas, c.state); 124 | canvas.setMatrix(curMatrix); 125 | } 126 | if (MainActivity.DEBUG) 127 | MainActivity.log("" + c.getClass()); 128 | c.draw(canvas); 129 | } 130 | catch (Exception e) {} // don't crash all of the rendering thread in case of bug 131 | commands[head] = null; 132 | head = (head + 1) % MAX_ITEMS; 133 | } 134 | } 135 | 136 | synchronized public String[] getStatus() { 137 | if (connected) 138 | return null; 139 | else 140 | return disconnectedStatus; 141 | } 142 | 143 | public void forceUpdate() { 144 | forcedUpdate = true; 145 | } 146 | 147 | synchronized public boolean haveStuffToDraw() { 148 | return forcedUpdate || 0 <= findAdjustedTail(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/Transformation.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Matrix; 5 | 6 | public class Transformation extends Matrix { 7 | int prevCanvasW=-1; 8 | int prevEmulatedW=-1; 9 | int prevCanvasH=-1; 10 | int prevEmulatedH=-1; 11 | byte prevRotate=-1; 12 | 13 | public Transformation() { 14 | super(); 15 | } 16 | 17 | public void update(Canvas c, DisplayState state) { 18 | int width = c.getWidth(); 19 | int height = c.getHeight(); 20 | if (state.width == prevEmulatedW && state.height == prevEmulatedH && 21 | state.rotate == prevRotate && width == prevCanvasW && height == prevCanvasH) 22 | return; 23 | 24 | if (state.width <= 1 || state.height <=1 || c.getWidth() <=1 || c.getHeight() <=1) 25 | return; 26 | 27 | setScale((float)(width)/(state.width), (float)(height)/(state.height)); 28 | preTranslate(0.5f,0.5f); 29 | postRotate(state.rotate * 90, width / 2f, height / 2f); 30 | if ((state.rotate & 3) == 1) { 31 | float delta = (height-width)/2f; 32 | postTranslate(-delta, -delta); 33 | } 34 | else if ((state.rotate & 3) == 3) { 35 | float delta = (height-width)/2f; 36 | postTranslate(delta, delta); 37 | } 38 | prevEmulatedW = state.width; 39 | prevEmulatedH = state.height; 40 | prevRotate = state.rotate; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/UsbService.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.hardware.usb.UsbDevice; 9 | import android.hardware.usb.UsbDeviceConnection; 10 | import android.hardware.usb.UsbManager; 11 | 12 | import com.felhr.usbserial.CDCSerialDevice; 13 | import com.felhr.usbserial.UsbSerialDevice; 14 | import com.felhr.usbserial.UsbSerialInterface; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | public class UsbService extends ConnectionService { 20 | public static final String ACTION_DEVICE_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; 21 | public static final String ACTION_DEVICE_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED"; 22 | public static final String ACTION_USB_READY = "mobi.omegacentauri.vectordisplay.USB_READY"; 23 | public static final String ACTION_DEVICE_PERMISSION_NOT_GRANTED = "mobi.omegacentauri.vectordisplay.USB_PERMISSION_NOT_GRANTED"; 24 | public static final String ACTION_USB_NOT_SUPPORTED = "mobi.omegacentauri.vectordisplay.USB_NOT_SUPPORTED"; 25 | public static final String ACTION_NO_USB = "mobi.omegacentauri.vectordisplay.NO_USB"; 26 | public static final String ACTION_CDC_DRIVER_NOT_WORKING = "mobi.omegacentauri.vectordisplay.ACTION_CDC_DRIVER_NOT_WORKING"; 27 | public static final String ACTION_USB_DEVICE_NOT_WORKING = "mobi.omegacentauri.vectordisplay.ACTION_USB_DEVICE_NOT_WORKING"; 28 | public static final int MESSAGE_FROM_SERIAL_PORT = 0; 29 | public static final int CTS_CHANGE = 1; 30 | public static final int DSR_CHANGE = 2; 31 | public static final int SYNC_READ = 3; 32 | private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; 33 | private static final int BAUD_RATE = 115200; // 115200*4; // 115200; // BaudRate. Change this value if you need 34 | public static boolean SERVICE_CONNECTED = false; 35 | 36 | private UsbManager usbManager; 37 | private UsbDevice device; 38 | private UsbDeviceConnection connection; 39 | private UsbSerialDevice serialPort; 40 | 41 | public boolean serialPortConnected; 42 | /* 43 | * Data received from serial port will be received here. 44 | */ 45 | private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { 46 | @Override 47 | public void onReceivedData(byte[] data) { 48 | feed(data); 49 | } 50 | }; 51 | 52 | @Override 53 | public void disconnectDevice() { 54 | super.disconnectDevice(); 55 | 56 | if (serialPort != null) 57 | serialPort.close(); 58 | } 59 | 60 | @Override 61 | public void onDestroy() { 62 | super.onDestroy(); 63 | if (serialPort != null) 64 | serialPort.close(); 65 | unregisterReceiver(usbReceiver); 66 | UsbService.SERVICE_CONNECTED = false; 67 | } 68 | 69 | /* 70 | * Different notifications from OS will be received here (USB attached, detached, permission responses...) 71 | * About BroadcastReceiver: http://developer.android.com/reference/android/content/BroadcastReceiver.html 72 | */ 73 | private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { 74 | @Override 75 | public void onReceive(Context arg0, Intent arg1) { 76 | if (arg1.getAction().equals(ACTION_USB_PERMISSION)) { 77 | boolean granted = arg1.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED); 78 | if (granted) // User accepted our USB connection. Try to open the device as a serial port 79 | { 80 | Intent intent = new Intent(ACTION_DEVICE_CONNECTED); 81 | arg0.sendBroadcast(intent); 82 | connection = usbManager.openDevice(device); 83 | new ConnectionThread().start(); 84 | } else // User not accepted our USB connection. Send an Intent to the Main Activity 85 | { 86 | Intent intent = new Intent(ACTION_DEVICE_PERMISSION_NOT_GRANTED); 87 | arg0.sendBroadcast(intent); 88 | } 89 | } else if (arg1.getAction().equals(ACTION_DEVICE_ATTACHED)) { 90 | if (!serialPortConnected) 91 | findSerialPortDevice(); // A USB device has been attached. Try to open it as a Serial port 92 | } else if (arg1.getAction().equals(ACTION_DEVICE_DETACHED)) { 93 | // Usb device was disconnected. send an intent to the Main Activity 94 | Intent intent = new Intent(ACTION_DEVICE_DISCONNECTED); 95 | arg0.sendBroadcast(intent); 96 | synchronized (this) { 97 | if (serialPortConnected) { 98 | serialPort.close(); // syncClose(); 99 | } 100 | serialPortConnected = false; 101 | } 102 | } 103 | } 104 | }; 105 | 106 | /* 107 | * onCreate will be executed when service is started. It configures an IntentFilter to listen for 108 | * incoming Intents (USB ATTACHED, USB DETACHED...) and it tries to open a serial port. 109 | */ 110 | @Override 111 | public void onCreate() { 112 | super.onCreate(); 113 | serialPortConnected = false; 114 | UsbService.SERVICE_CONNECTED = true; 115 | setFilter(); 116 | usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 117 | findSerialPortDevice(); 118 | } 119 | 120 | /* MUST READ about services 121 | * http://developer.android.com/guide/components/services.html 122 | * http://developer.android.com/guide/components/bound-services.html 123 | */ 124 | /* 125 | * This function will be called from MainActivity to write data through Serial Port 126 | */ 127 | @Override 128 | public void write(byte[] data) { 129 | if (serialPort != null) 130 | serialPort.write(data); 131 | } 132 | 133 | /* 134 | * This function will be called from MainActivity to change baud rate 135 | */ 136 | 137 | public void changeBaudRate(int baudRate){ 138 | if(serialPort != null) 139 | serialPort.setBaudRate(baudRate); 140 | } 141 | 142 | public void close() { 143 | if (serialPort != null) 144 | serialPort.close(); 145 | stopSelf(); 146 | } 147 | 148 | private void findSerialPortDevice() { 149 | // This snippet will try to open the first encountered usb device connected, excluding usb root hubs 150 | HashMap usbDevices = usbManager.getDeviceList(); 151 | if (!usbDevices.isEmpty()) { 152 | boolean keep = true; 153 | for (Map.Entry entry : usbDevices.entrySet()) { 154 | device = entry.getValue(); 155 | int deviceVID = device.getVendorId(); 156 | int devicePID = device.getProductId(); 157 | 158 | if (deviceVID != 0x1d6b && (devicePID != 0x0001 && devicePID != 0x0002 && devicePID != 0x0003)) { 159 | // There is a device connected to our Android device. Try to open it as a Serial Port. 160 | requestUserPermission(); 161 | keep = false; 162 | } else { 163 | connection = null; 164 | device = null; 165 | } 166 | 167 | if (!keep) 168 | break; 169 | } 170 | if (!keep) { 171 | // There is no USB devices connected (but usb host were listed). Send an intent to MainActivity. 172 | broadcast(ACTION_NO_USB); 173 | } 174 | } else { 175 | // There is no USB devices connected. Send an intent to MainActivity 176 | broadcast(ACTION_NO_USB); 177 | } 178 | } 179 | 180 | private void setFilter() { 181 | IntentFilter filter = new IntentFilter(); 182 | filter.addAction(ACTION_USB_PERMISSION); 183 | filter.addAction(ACTION_DEVICE_DETACHED); 184 | filter.addAction(ACTION_DEVICE_ATTACHED); 185 | registerReceiver(usbReceiver, filter); 186 | } 187 | 188 | /* 189 | * Request user permission. The response will be received in the BroadcastReceiver 190 | */ 191 | private void requestUserPermission() { 192 | PendingIntent mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); 193 | usbManager.requestPermission(device, mPendingIntent); 194 | } 195 | 196 | /* 197 | * A simple thread to open a serial port. 198 | * Although it should be a fast operation. moving usb operations away from UI thread is a good thing. 199 | */ 200 | private class ConnectionThread extends Thread { 201 | @Override 202 | public void run() { 203 | serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection); 204 | if (serialPort != null) { 205 | if (serialPort.open()) { 206 | serialPortConnected = true; 207 | serialPort.setBaudRate(BAUD_RATE); 208 | serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8); 209 | serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1); 210 | serialPort.setParity(UsbSerialInterface.PARITY_NONE); 211 | /** 212 | * Current flow control Options: 213 | * UsbSerialInterface.FLOW_CONTROL_OFF 214 | * UsbSerialInterface.FLOW_CONTROL_RTS_CTS only for CP2102 and FT232 215 | * UsbSerialInterface.FLOW_CONTROL_DSR_DTR only for CP2102 and FT232 216 | */ 217 | serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF); 218 | serialPort.read(mCallback); 219 | //serialPort.getCTS(ctsCallback); 220 | //serialPort.getDSR(dsrCallback); 221 | 222 | // Some Arduinos would need some sleep because firmware wait some time to know whether a new sketch is going 223 | // to be uploaded or not 224 | //Thread.sleep(2000); // sleep some. YMMV with different chips. 225 | 226 | // Everything went as expected. Send an intent to MainActivity 227 | broadcast(ACTION_USB_READY); 228 | } else { 229 | // Serial port could not be opened, maybe an I/O error or if CDC driver was chosen, it does not really fit 230 | // Send an Intent to Main Activity 231 | if (serialPort instanceof CDCSerialDevice) { 232 | broadcast(ACTION_CDC_DRIVER_NOT_WORKING); 233 | } else { 234 | broadcast(ACTION_USB_DEVICE_NOT_WORKING); 235 | } 236 | } 237 | } else { 238 | // No driver for given device, even generic CDC driver could not be loaded 239 | broadcast(ACTION_USB_NOT_SUPPORTED); 240 | } 241 | } 242 | } 243 | } 244 | 245 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/VectorAPI.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | 6 | import mobi.omegacentauri.vectordisplay.commands.*; 7 | 8 | public class VectorAPI { 9 | public DisplayState state; 10 | private Class[] map = new Class[256]; 11 | private Command currentCommand = null; 12 | public MyBuffer buffer = new MyBuffer(); 13 | private static final int TIMEOUT = 5000; 14 | private long commandStartTime; 15 | private Activity context; 16 | private byte lastChar = 0; 17 | public static final byte RESET_COMMAND = 'E'; 18 | public static final byte INITIALIZE_COMMAND = 'H'; 19 | public static final byte ATTRIBUTE32_COMMAND = 'B'; 20 | public static final byte INITIALIZE_WITH_RESOLUTION_COMMAND = 'Z'; 21 | 22 | public VectorAPI(Activity context) { 23 | this.context = context; 24 | this.state = new DisplayState(); 25 | // need to have capital letters 26 | for(int i=0;i cl = map[lastChar & 0xFF]; 78 | lastChar = 0; 79 | if (cl != null) { 80 | Command c; 81 | try { 82 | c = (Command) cl.getConstructor(DisplayState.class).newInstance(state); 83 | } catch (Exception e) { 84 | Log.e("VectorDisplay", e.toString()); 85 | c = null; 86 | } 87 | if (c != null) { 88 | currentCommand = c; 89 | commandStartTime = System.currentTimeMillis(); 90 | buffer.clear(); 91 | } 92 | } 93 | } 94 | else { 95 | if (map[ch & 0xFF] != null) { 96 | lastChar = ch; 97 | } 98 | else { 99 | // MainActivity.log("Unparsed char "+Integer.toHexString(ch&0xFF)); 100 | lastChar = 0; 101 | } 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | public static class MyBuffer { 108 | static final int MAX_BUFFER = 1024*256; 109 | public byte[] data = new byte[MAX_BUFFER]; 110 | public int length = 0; 111 | public boolean lowEndian = true; 112 | public static final char[] CP437 = { 113 | 0x0000,0x0001,0x0002,0x0003,0x0004,0x0005,0x0006,0x0007,0x0008,0x0009,0x000a,0x000b,0x000c,0x000d,0x000e,0x000f, 114 | 0x0010,0x0011,0x0012,0x0013,0x0014,0x0015,0x0016,0x0017,0x0018,0x0019,0x001a,0x001b,0x001c,0x001d,0x001e,0x001f, 115 | 0x0020,0x0021,0x0022,0x0023,0x0024,0x0025,0x0026,0x0027,0x0028,0x0029,0x002a,0x002b,0x002c,0x002d,0x002e,0x002f, 116 | 0x0030,0x0031,0x0032,0x0033,0x0034,0x0035,0x0036,0x0037,0x0038,0x0039,0x003a,0x003b,0x003c,0x003d,0x003e,0x003f, 117 | 0x0040,0x0041,0x0042,0x0043,0x0044,0x0045,0x0046,0x0047,0x0048,0x0049,0x004a,0x004b,0x004c,0x004d,0x004e,0x004f, 118 | 0x0050,0x0051,0x0052,0x0053,0x0054,0x0055,0x0056,0x0057,0x0058,0x0059,0x005a,0x005b,0x005c,0x005d,0x005e,0x005f, 119 | 0x0060,0x0061,0x0062,0x0063,0x0064,0x0065,0x0066,0x0067,0x0068,0x0069,0x006a,0x006b,0x006c,0x006d,0x006e,0x006f, 120 | 0x0070,0x0071,0x0072,0x0073,0x0074,0x0075,0x0076,0x0077,0x0078,0x0079,0x007a,0x007b,0x007c,0x007d,0x007e,0x007f, 121 | 0x00c7,0x00fc,0x00e9,0x00e2,0x00e4,0x00e0,0x00e5,0x00e7,0x00ea,0x00eb,0x00e8,0x00ef,0x00ee,0x00ec,0x00c4,0x00c5, 122 | 0x00c9,0x00e6,0x00c6,0x00f4,0x00f6,0x00f2,0x00fb,0x00f9,0x00ff,0x00d6,0x00dc,0x00a2,0x00a3,0x00a5,0x20a7,0x0192, 123 | 0x00e1,0x00ed,0x00f3,0x00fa,0x00f1,0x00d1,0x00aa,0x00ba,0x00bf,0x2310,0x00ac,0x00bd,0x00bc,0x00a1,0x00ab,0x00bb, 124 | 0x2591,0x2592,0x2593,0x2502,0x2524,0x2561,0x2562,0x2556,0x2555,0x2563,0x2551,0x2557,0x255d,0x255c,0x255b,0x2510, 125 | 0x2514,0x2534,0x252c,0x251c,0x2500,0x253c,0x255e,0x255f,0x255a,0x2554,0x2569,0x2566,0x2560,0x2550,0x256c,0x2567, 126 | 0x2568,0x2564,0x2565,0x2559,0x2558,0x2552,0x2553,0x256b,0x256a,0x2518,0x250c,0x2588,0x2584,0x258c,0x2590,0x2580, 127 | 0x03b1,0x00df,0x0393,0x03c0,0x03a3,0x03c3,0x00b5,0x03c4,0x03a6,0x0398,0x03a9,0x03b4,0x221e,0x03c6,0x03b5,0x2229, 128 | 0x2261,0x00b1,0x2265,0x2264,0x2320,0x2321,0x00f7,0x2248,0x00b0,0x2219,0x00b7,0x221a,0x207f,0x00b2,0x25a0,0x00a0, 129 | }; 130 | 131 | void clear() { 132 | length = 0; 133 | } 134 | 135 | boolean put(byte b) { 136 | if (length < MAX_BUFFER) { 137 | data[length++] = b; 138 | return true; 139 | } 140 | else { 141 | return false; 142 | } 143 | } 144 | 145 | public boolean checksum() { 146 | int sum = 0; 147 | for (int i = 0; i< length -1; i++) { 148 | sum = ((data[i] & 0xFF) + sum) & 0xFF; 149 | } 150 | sum ^= 0xFF; 151 | // Log.v("VectorDisplay", "checksum "+data[length-1]+" vs "+sum); 152 | return data[length -1] == (byte)sum; 153 | } 154 | 155 | public short getShort(int start) { 156 | if (lowEndian) 157 | return (short) ((data[start] & 0xFF) | (data[start+1] << 8)); 158 | else 159 | return (short) ((data[start+1] & 0xFF) | (data[start] << 8)); 160 | } 161 | 162 | public int getInt(int start) { 163 | if (lowEndian) 164 | return (data[start] & 0xFF) | ((data[start+1] & 0xFF) << 8) | 165 | ((data[start+2] & 0xFF) << 16) | ((data[start+3] & 0xFF) << 24); 166 | else 167 | return (data[start+3] & 0xFF) | ((data[start+2] & 0xFF) << 8) | 168 | ((data[start+1] & 0xFF) << 16) | ((data[start] & 0xFF) << 24); 169 | } 170 | 171 | String getString(int start, DisplayState state) { 172 | return getString(start, length -start, state); 173 | } 174 | 175 | public String getString(int start, int length, DisplayState state) { 176 | if (state.cp437) { 177 | char[] out = new char[length]; 178 | for (int i=0; i>5) & ((1<<6)-1)) * 255 + (1<<5)) / ((1<<6)-1); 204 | int b = ((c>>11) * 255 + (1<<4)) / ((1<<5)-1); 205 | return (0xFF<<24)|(r<<16)|(g<<8)|b; 206 | } 207 | 208 | public float getFixed32(int i) { 209 | return getInt(i) / 65536f; 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/VectorView.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.graphics.Typeface; 12 | import android.os.Build; 13 | import android.text.TextPaint; 14 | import android.util.AttributeSet; 15 | import android.util.Log; 16 | import android.util.TypedValue; 17 | import android.view.MotionEvent; 18 | import android.view.Surface; 19 | import android.view.SurfaceHolder; 20 | import android.view.SurfaceView; 21 | import android.view.View; 22 | 23 | import static android.util.TypedValue.*; 24 | 25 | /** 26 | * Created by Alexander_Pruss on 10/13/2017. 27 | */ 28 | 29 | public class VectorView extends SurfaceView implements SurfaceHolder.Callback { 30 | RecordAndPlay record; 31 | float aspectRatio = 4f/3f; 32 | MainActivity main; 33 | static final int MOTION_TIMING = 30; 34 | static final byte[] UP = new byte[] { 'U', 'P'}; 35 | static final byte[] DOWN = new byte[] { 'D', 'N'}; 36 | static final byte[] MOVE = new byte[] { 'M', 'V'}; 37 | long lastEvent = -MOTION_TIMING; 38 | byte[] outBuf = new byte[8]; 39 | float[] coords = new float[2]; 40 | TextPaint statusPaint; 41 | MySurfaceThread thread; 42 | private Canvas savedCanvas; 43 | Paint paint = new Paint(); 44 | volatile long waitForSurfaceChanged = -1; 45 | 46 | @Override 47 | protected void onMeasure(int wspec, int hspec) { 48 | int w = View.MeasureSpec.getSize(wspec); 49 | int h = View.MeasureSpec.getSize(hspec); 50 | MainActivity.log( "onmeasure "+w+" "+h+" want "+aspectRatio ); 51 | if (h != 0 && aspectRatio != 0f) { 52 | if ((float) w / h >= aspectRatio) { 53 | w = (int) (h * aspectRatio); 54 | } else { 55 | h = (int) (w / aspectRatio); 56 | } 57 | } 58 | if (w==0) 59 | w=1; 60 | if (h==0) 61 | h=1; 62 | setMeasuredDimension(w, h); 63 | } 64 | 65 | /* @Override 66 | public void onLayout(boolean changed, int l, int t, int r, int b) { 67 | super.onLayout(changed, l, t, r, b); 68 | MainActivity.log("onLayout1"); 69 | } */ 70 | 71 | public VectorView(Context c, AttributeSet set) { 72 | super(c,set); 73 | 74 | Log.v("VectorDisplay", "creating vector view"); 75 | 76 | getHolder().addCallback(this); 77 | setZOrderOnTop(true); 78 | 79 | main = (MainActivity)c; 80 | 81 | statusPaint = new TextPaint(); 82 | statusPaint.setStyle(Paint.Style.FILL); 83 | statusPaint.setTypeface(Typeface.MONOSPACE); 84 | statusPaint.setColor(Color.YELLOW); 85 | statusPaint.setTextAlign(Paint.Align.CENTER); 86 | 87 | //this.redraw = true; 88 | this.record = main.record; 89 | 90 | setOnTouchListener(new OnTouchListener() { 91 | @Override 92 | public boolean onTouch(View v, MotionEvent event) { 93 | if (record == null || record.parser == null || record.parser.state == null) 94 | return true; 95 | if (event.getAction() == MotionEvent.ACTION_UP) { 96 | outBuf[0] = 'U'; 97 | outBuf[1] = 'P'; 98 | } else if (event.getAction() == MotionEvent.ACTION_DOWN) { 99 | outBuf[0] = 'D'; 100 | outBuf[1] = 'N'; 101 | } else if (event.getAction() == MotionEvent.ACTION_MOVE && 102 | System.currentTimeMillis() >= lastEvent + MOTION_TIMING) { 103 | outBuf[0] = 'M'; 104 | outBuf[1] = 'V'; 105 | } else { 106 | return true; 107 | } 108 | lastEvent = System.currentTimeMillis(); 109 | Matrix inv = new Matrix(); 110 | synchronized(record.curMatrix) { 111 | if (!record.curMatrix.invert(inv)) 112 | inv.reset(); 113 | } 114 | coords[0] = event.getX(); 115 | coords[1] = event.getY(); 116 | inv.mapPoints(coords); 117 | int x = (int)(coords[0]+0.5f); 118 | int y = (int)(coords[1]+0.5f); 119 | 120 | if (record.parser.buffer.lowEndian) { 121 | outBuf[2] = (byte) (x & 0xFF); 122 | outBuf[3] = (byte) (x >> 8); 123 | outBuf[4] = (byte) (y & 0xFF); 124 | outBuf[5] = (byte) (y >> 8); 125 | } 126 | else { 127 | outBuf[3] = (byte) (x & 0xFF); 128 | outBuf[2] = (byte) (x >> 8); 129 | outBuf[5] = (byte) (y & 0xFF); 130 | outBuf[4] = (byte) (y >> 8); 131 | } 132 | outBuf[6] = 0; 133 | outBuf[7] = 0; 134 | synchronized(main) { 135 | if (main.connectionService != null) 136 | main.connectionService.write(outBuf); 137 | } 138 | return true; 139 | } 140 | }); 141 | } 142 | 143 | public void updateStatus(Canvas canvas) { 144 | String[] status = record.getStatus(); 145 | if (status != null) { 146 | int w = canvas.getWidth(); 147 | statusPaint.setTextSize(w / 15f); 148 | float lineHeight = -(statusPaint.getFontMetrics().top - statusPaint.getFontMetrics().bottom) * 1.1f; 149 | float y = canvas.getHeight() - lineHeight * (status.length - 1) - statusPaint.descent(); 150 | for (String line : status) { 151 | statusPaint.setTextSize(w / 15f); 152 | float m = statusPaint.measureText(line); 153 | if (m > w) 154 | statusPaint.setTextSize(w / 15f * w / m); 155 | canvas.drawText(line, canvas.getWidth() / 2, y, statusPaint); 156 | y += lineHeight; 157 | } 158 | } 159 | } 160 | 161 | @Override 162 | public void surfaceCreated(SurfaceHolder holder) { 163 | Log.v("VectorDisplay", "surfaceCreated"); 164 | thread = new MySurfaceThread(holder); 165 | thread.start(); 166 | } 167 | 168 | @Override 169 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 170 | Log.v("VectorDisplay", "surfaceChanged"); 171 | record.forceUpdate(); 172 | waitForSurfaceChanged = -1; 173 | } 174 | 175 | @Override 176 | public void surfaceDestroyed(SurfaceHolder holder) { 177 | if (thread != null) { 178 | thread.close(); 179 | thread = null; 180 | } 181 | } 182 | 183 | public class MySurfaceThread extends Thread { 184 | private SurfaceHolder holder; 185 | volatile private boolean active; 186 | private Bitmap bitmap; 187 | 188 | public MySurfaceThread(SurfaceHolder h) { 189 | holder = h; 190 | active = true; 191 | } 192 | 193 | synchronized public void close() { 194 | active = false; 195 | } 196 | 197 | private void prepareBitmap(Canvas c) { 198 | int w = c.getWidth(); 199 | int h = c.getHeight(); 200 | if (bitmap == null || bitmap.getWidth() != w || bitmap.getHeight() != h) { 201 | Bitmap old = bitmap; 202 | bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 203 | savedCanvas = new Canvas(); 204 | savedCanvas.setBitmap(bitmap); 205 | if (old != null) { 206 | Rect newR = new Rect(0, 0, w, h); 207 | savedCanvas.drawBitmap(old, null, newR, null); 208 | old.recycle(); 209 | } 210 | } 211 | } 212 | 213 | private void deleteBitmap() { 214 | if (bitmap != null) { 215 | bitmap.recycle(); 216 | bitmap = null; 217 | } 218 | } 219 | 220 | @Override 221 | public void run() { 222 | while(active) { 223 | boolean drew = false; 224 | long t0 = System.currentTimeMillis(); 225 | synchronized (record) { 226 | if (record.haveStuffToDraw() && active) { // check we're still active 227 | try { 228 | Canvas c; 229 | if (Build.VERSION.SDK_INT >= 23) 230 | c = holder.getSurface().lockHardwareCanvas(); 231 | else 232 | c = holder.lockCanvas(); 233 | prepareBitmap(c); 234 | record.draw(savedCanvas); 235 | c.drawBitmap(bitmap, 0f, 0f, paint); 236 | updateStatus(c); 237 | holder.unlockCanvasAndPost(c); 238 | drew = true; 239 | } catch (Exception e) { 240 | // in case surface gets invalidated mid-way 241 | } 242 | } 243 | } 244 | long t = System.currentTimeMillis()-t0; 245 | long pauseTime = drew ? record.updateTimeMillis : 2; 246 | if (0 <= t && t < pauseTime) { 247 | try { 248 | sleep(pauseTime-t); 249 | } catch (InterruptedException e) { 250 | } 251 | } 252 | } 253 | deleteBitmap(); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/WifiService.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.hardware.usb.UsbDevice; 9 | import android.hardware.usb.UsbDeviceConnection; 10 | import android.hardware.usb.UsbManager; 11 | import android.net.wifi.WifiInfo; 12 | import android.net.wifi.WifiManager; 13 | import android.util.Log; 14 | 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.net.ServerSocket; 19 | import java.net.Socket; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | public class WifiService extends ConnectionService { 24 | public static final int PORT = 7788; 25 | public static boolean SERVICE_CONNECTED = false; 26 | public boolean stop = false; 27 | public String ipAddress = ""; 28 | 29 | private WifiServer server; 30 | private WifiInfo wifiInfo; 31 | 32 | @Override 33 | synchronized public void onDestroy() { 34 | super.onDestroy(); 35 | if (server != null) 36 | server.close(); 37 | stop = true; 38 | WifiService.SERVICE_CONNECTED = false; 39 | } 40 | 41 | 42 | @Override 43 | public void onCreate() { 44 | super.onCreate(); 45 | Log.v("VectorDisplay", "on create WiFiService"); 46 | WifiService.SERVICE_CONNECTED = true; 47 | new WifiService.ConnectionThread().start(); 48 | } 49 | 50 | @Override 51 | synchronized public void write(byte[] data) { 52 | if (server != null) { // TODO: fix synchronization 53 | server.write(data); 54 | } 55 | } 56 | 57 | /* 58 | * This function will be called from MainActivity to change baud rate 59 | */ 60 | 61 | @Override 62 | public void disconnectDevice() { 63 | if (server != null && server.mClient != null) { 64 | try { 65 | server.mClient.close(); 66 | server.mClient = null; 67 | } catch (IOException e) { 68 | } 69 | } 70 | } 71 | 72 | @Override 73 | synchronized public void close() { 74 | if (server != null) 75 | server.close(); 76 | stop = true; 77 | stopSelf(); 78 | } 79 | 80 | @Override 81 | public void setRecord(RecordAndPlay r) { 82 | super.setRecord(r); 83 | if (wifiInfo != null && wifiInfo.getNetworkId() != -1) { 84 | int ip = wifiInfo.getIpAddress(); 85 | if (record != null) { 86 | record.setDisconnectedStatus(new String[]{"WiFi device not connected", "Connect to " + ipString(ip)}); 87 | record.forceUpdate(); 88 | } 89 | } 90 | } 91 | 92 | private static String ipString(int ip) { 93 | return "" + (0xFF & ip) + "." + (0xFF & (ip >> 8)) + "." + (0xFF & (ip >> 16)) + "." + (0xFF & (ip >> 24)); 94 | } 95 | 96 | private class ConnectionThread extends Thread { 97 | @Override 98 | public void run() { 99 | boolean done = false; 100 | 101 | do { 102 | server = null; 103 | 104 | WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE); 105 | int net = -1; 106 | int ip = 0; 107 | if (wifiManager != null) { 108 | wifiInfo = wifiManager.getConnectionInfo(); 109 | net = wifiInfo.getNetworkId(); 110 | if (net != -1) 111 | ip = wifiInfo.getIpAddress(); 112 | } 113 | else 114 | wifiInfo = null; 115 | 116 | if (ip != 0) { 117 | if (record != null) { 118 | record.setDisconnectedStatus(new String[]{"WiFi device not connected", "Connect to " + ipString(ip)}); 119 | MainActivity.log("ip "+ipString(ip)); 120 | record.forceUpdate(); 121 | } 122 | 123 | try { 124 | synchronized (WifiService.this) { 125 | done = true; 126 | server = new WifiServer(WifiService.this, PORT); 127 | } 128 | server.start(); 129 | } catch (Exception e) { 130 | if (server != null) { 131 | server.stop(); 132 | server = null; 133 | } 134 | } 135 | 136 | if (!done) { 137 | synchronized (WifiService.this) { 138 | if (WifiService.this.stop) 139 | break; 140 | } 141 | } 142 | } 143 | else { 144 | if (record != null) 145 | record.setDisconnectedStatus(new String[]{"WiFi not connected"}); 146 | } 147 | 148 | try { 149 | Thread.sleep(1000); 150 | } catch (InterruptedException e) { 151 | } 152 | } while(!done); 153 | 154 | Intent intent = new Intent(ACTION_DEVICE_DISCONNECTED); // TODO: more informative 155 | context.sendBroadcast(intent); 156 | } 157 | } 158 | public class WifiServer extends Thread { 159 | private ServerSocket listener; 160 | WifiService wifiService; 161 | Socket mClient = null; 162 | InputStream in = null; 163 | OutputStream out = null; 164 | byte[] byteBuffer = new byte[256]; 165 | Boolean stop; 166 | 167 | public WifiServer(WifiService service, int port) throws IOException { 168 | listener = new ServerSocket(port); 169 | wifiService = service; 170 | } 171 | 172 | @Override 173 | public void run() { 174 | stop = false; 175 | while(! stop) { 176 | synchronized(wifiService) { 177 | if (wifiService.stop) 178 | break; 179 | } 180 | try { 181 | WifiServer.this.mClient = listener.accept(); 182 | Socket client = WifiServer.this.mClient; // local copy 183 | Log.v("VectorDisplay", "Accepted"); 184 | in = client.getInputStream(); 185 | synchronized(WifiServer.this) { 186 | out = client.getOutputStream(); 187 | } 188 | wifiService.broadcast(ConnectionService.ACTION_DEVICE_CONNECTED); 189 | while(client.isConnected()) { 190 | int n = in.read(byteBuffer); 191 | wifiService.record.feed(byteBuffer, n); 192 | } 193 | } catch (IOException e) { 194 | Log.v("VectorDisplay", "disconnected client"); 195 | } 196 | stopClient(); 197 | } 198 | } 199 | 200 | public void close() { 201 | stopClient(); 202 | stop = true; 203 | if (listener != null) { 204 | try { 205 | listener.close(); 206 | } catch (IOException e) { 207 | listener = null; 208 | } 209 | } 210 | } 211 | 212 | public void stopClient() { 213 | wifiService.broadcast(ACTION_DEVICE_DISCONNECTED); 214 | if (mClient != null) { 215 | try { 216 | mClient.close(); 217 | } catch (IOException e1) { 218 | } 219 | mClient = null; 220 | } 221 | } 222 | 223 | synchronized public void write(byte[] data) { 224 | if (out != null) { 225 | try { 226 | out.write(data); 227 | } catch (IOException e) { 228 | } 229 | } 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/AddButton.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | 8 | import mobi.omegacentauri.vectordisplay.DisplayState; 9 | import mobi.omegacentauri.vectordisplay.MainActivity; 10 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 11 | 12 | public class AddButton extends Command { 13 | byte cmd; 14 | String label; 15 | 16 | public AddButton(DisplayState state) { 17 | super(state); 18 | } 19 | 20 | @Override 21 | public int fixedArgumentsLength() { 22 | return 1; 23 | } 24 | 25 | @Override 26 | public boolean haveStringArgument() { 27 | return true; 28 | } 29 | 30 | @Override 31 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 32 | cmd = buffer.data[0]; 33 | label = buffer.getString(1, buffer.length-1-1, state); 34 | return state; 35 | } 36 | 37 | @Override 38 | public boolean handleCommand(Handler h) { 39 | Message msg = h.obtainMessage(MainActivity.ADD_COMMAND); 40 | Bundle b = new Bundle(); 41 | b.putString(MainActivity.KEY_LABEL, label); 42 | b.putByte(MainActivity.KEY_COMMAND, cmd); 43 | msg.setData(b); 44 | h.sendMessage(msg); 45 | return true; 46 | } 47 | 48 | @Override 49 | public boolean doesDraw() { // does this type actually do anything when show() is called? 50 | return false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/Arc.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.RectF; 7 | import android.util.Log; 8 | 9 | import mobi.omegacentauri.vectordisplay.DisplayState; 10 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 11 | 12 | import static mobi.omegacentauri.vectordisplay.commands.RoundedRectangle.rect; 13 | 14 | public class Arc extends Command { 15 | short x, y, r; 16 | float startAngle; 17 | float sweepAngle; 18 | RectF oval = new RectF(); 19 | boolean filled; 20 | static Paint filledPaint = DefaultFilledPaint(); 21 | static Paint strokePaint = DefaultStrokePaint(); 22 | 23 | private static Paint DefaultFilledPaint() { 24 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 25 | p.setStyle(Paint.Style.FILL); 26 | p.setStrokeWidth(0); 27 | return p; 28 | } 29 | 30 | private static Paint DefaultStrokePaint() { 31 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 32 | p.setStyle(Paint.Style.STROKE); 33 | return p; 34 | } 35 | 36 | public Arc(DisplayState state) { 37 | super(state); 38 | } 39 | 40 | @Override 41 | public int fixedArgumentsLength() { 42 | return 15; 43 | } 44 | 45 | @Override 46 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 47 | x = buffer.getShort(0); 48 | y = buffer.getShort(2); 49 | r = buffer.getShort(4); 50 | startAngle = buffer.getFixed32(6); 51 | sweepAngle = buffer.getFixed32(10); 52 | filled = buffer.data[14] != 0; 53 | 54 | return state; 55 | } 56 | 57 | @Override 58 | public void draw(Canvas c) { 59 | oval.left = x-r; 60 | oval.right = x+r; 61 | oval.top = y-r; 62 | oval.bottom = y+r; 63 | 64 | if (filled) { 65 | filledPaint.setColor(state.foreColor); 66 | c.drawArc(oval, startAngle, sweepAngle, true, filledPaint); 67 | } 68 | else { 69 | strokePaint.setColor(state.foreColor); 70 | strokePaint.setStrokeWidth(state.thickness); 71 | strokePaint.setStrokeCap(state.rounded ? Paint.Cap.ROUND : Paint.Cap.SQUARE); 72 | strokePaint.setStrokeJoin(state.rounded ? Paint.Join.ROUND : Paint.Join.MITER); 73 | filledPaint.setColor(state.foreColor); 74 | c.drawArc(oval, startAngle, sweepAngle, false, strokePaint); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/Attribute16.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import mobi.omegacentauri.vectordisplay.DisplayState; 4 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 5 | 6 | import android.app.Activity; 7 | import android.graphics.Canvas; 8 | 9 | public class Attribute16 extends Command { 10 | boolean resetView = false; 11 | 12 | public Attribute16(DisplayState state) { 13 | super(state); 14 | } 15 | 16 | @Override 17 | public int fixedArgumentsLength() { 18 | return 3; 19 | } 20 | 21 | @Override 22 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 23 | switch((char)buffer.data[0]) { 24 | case 'b': 25 | state.backColor = buffer.getColor565(1); 26 | break; 27 | case 'k': 28 | state.textBackColor = buffer.getColor565(1); 29 | break; 30 | case 'F': 31 | state.textForeColor = buffer.getColor565(1); 32 | break; 33 | case 'f': 34 | state.foreColor = buffer.getColor565(1); 35 | break; 36 | } 37 | return state; 38 | } 39 | 40 | @Override 41 | public boolean doesDraw() { 42 | return resetView; 43 | } 44 | 45 | @Override 46 | public void draw(Canvas c) { 47 | Clear.clearCanvas(c, state); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/Attribute32.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Canvas; 5 | import android.os.Handler; 6 | 7 | import mobi.omegacentauri.vectordisplay.DisplayState; 8 | import mobi.omegacentauri.vectordisplay.MainActivity; 9 | import mobi.omegacentauri.vectordisplay.RecordAndPlay; 10 | import mobi.omegacentauri.vectordisplay.VectorAPI; 11 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 12 | 13 | public class Attribute32 extends Command { 14 | boolean resetView = false; 15 | boolean ack = false; 16 | 17 | public Attribute32(DisplayState state) { 18 | super(state); 19 | } 20 | 21 | @Override 22 | public int fixedArgumentsLength() { 23 | return 5; 24 | } 25 | 26 | @Override 27 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 28 | switch((char)buffer.data[0]) { 29 | case 't': 30 | state.thickness = buffer.getFixed32(1); 31 | break; 32 | case 'c': 33 | state.width = buffer.getShort(1); 34 | state.height = buffer.getShort(3); 35 | ack = true; 36 | resetView = true; 37 | break; 38 | case 'a': 39 | state.pixelAspectRatio = buffer.getFixed32(1); 40 | resetView = true; 41 | break; 42 | case 's': 43 | state.textSize = buffer.getFixed32(1); 44 | break; 45 | case 'b': 46 | state.backColor = buffer.getInt(1); 47 | break; 48 | case 'k': 49 | state.textBackColor = buffer.getInt(1); 50 | break; 51 | case 'F': 52 | state.textForeColor = buffer.getInt(1); 53 | break; 54 | case 'f': 55 | state.foreColor = buffer.getInt(1); 56 | break; 57 | case 'x': 58 | state.cursorX = buffer.getFixed32(1); 59 | break; 60 | case 'y': 61 | state.cursorY = buffer.getFixed32(1); 62 | break; 63 | } 64 | return state; 65 | } 66 | 67 | @Override 68 | public boolean doesDraw() { 69 | return resetView; 70 | } 71 | 72 | // @Override 73 | // public boolean needToResetView() { return resetView; } 74 | 75 | @Override 76 | public void draw(Canvas c) { 77 | Clear.clearCanvas(c, state); 78 | } 79 | 80 | @Override 81 | public boolean handleCommand(Handler h) { 82 | if (resetView) 83 | MainActivity.sendResetViewMessage(h, state); 84 | if (ack) 85 | sendAck(h, VectorAPI.ATTRIBUTE32_COMMAND, RecordAndPlay.LAYOUT_DELAY); 86 | return resetView; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/Attribute8.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | 5 | import mobi.omegacentauri.vectordisplay.DisplayState; 6 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 7 | 8 | public class Attribute8 extends Command { 9 | public Attribute8(DisplayState state) { 10 | super(state); 11 | } 12 | 13 | @Override 14 | public int fixedArgumentsLength() { 15 | return 2; 16 | } 17 | 18 | @Override 19 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 20 | switch((char)buffer.data[0]) { 21 | case 'h': 22 | state.hAlignText = (char)buffer.data[1]; 23 | break; 24 | case 'v': 25 | state.vAlignText = (char)buffer.data[1]; 26 | break; 27 | case 'b': 28 | if (buffer.data[1] != 0) 29 | state.fontInfo |= DisplayState.FONT_BOLD; 30 | else 31 | state.fontInfo &= ~DisplayState.FONT_BOLD; 32 | break; 33 | case 'I': 34 | if (buffer.data[1] != 0) 35 | state.fontInfo |= DisplayState.FONT_ITALIC; 36 | else 37 | state.fontInfo &= ~DisplayState.FONT_ITALIC; 38 | break; 39 | case 'f': 40 | state.fontInfo = buffer.data[1]; 41 | break; 42 | case 'F': 43 | state.fontInfo = (byte) ((state.fontInfo & DisplayState.FONT_TYPEFACE_MASK) | (buffer.data[1] & DisplayState.FONT_TYPEFACE_MASK)); 44 | break; 45 | case 'n': 46 | state.rounded = buffer.data[1] != 0; 47 | break; 48 | case 'o': 49 | state.opaqueTextBackground = buffer.data[1] != 0; 50 | break; 51 | case 'r': 52 | state.rotate = (byte) buffer.data[1]; 53 | break; 54 | case 'i': 55 | state.cp437 = buffer.data[1] != 0; 56 | break; 57 | case 'c': 58 | state.continuousUpdate = buffer.data[1] != 0; 59 | // Log.v("VectorDisplay", "cupdate "+state.continuousUpdate); 60 | break; 61 | case 'w': 62 | state.wrap = buffer.data[1] != 0; 63 | } 64 | return state; 65 | } 66 | 67 | @Override 68 | public boolean doesDraw() { 69 | return false; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/Circle.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | 7 | import mobi.omegacentauri.vectordisplay.DisplayState; 8 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 9 | 10 | public class Circle extends Command { 11 | short x,y,r; 12 | static Paint p = DefaultPaint(); 13 | 14 | private static Paint DefaultPaint() { 15 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 16 | p.setStyle(Paint.Style.STROKE); 17 | return p; 18 | } 19 | 20 | public Circle(DisplayState state) { 21 | super(state); 22 | } 23 | 24 | @Override 25 | public int fixedArgumentsLength() { 26 | return 6; 27 | } 28 | 29 | @Override 30 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 31 | x = buffer.getShort(0); 32 | y = buffer.getShort(2); 33 | r = buffer.getShort(4); 34 | return state; 35 | } 36 | 37 | @Override 38 | public void draw(Canvas c) { 39 | p.setColor(state.foreColor); 40 | p.setStrokeWidth(state.thickness); 41 | 42 | c.drawCircle(x, y, r, p); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/Clear.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import mobi.omegacentauri.vectordisplay.DisplayState; 4 | import mobi.omegacentauri.vectordisplay.MainActivity; 5 | import mobi.omegacentauri.vectordisplay.commands.Command; 6 | 7 | import android.graphics.Canvas; 8 | import android.graphics.Paint; 9 | 10 | import static java.lang.Math.max; 11 | 12 | public class Clear extends Command { 13 | public Clear(DisplayState state) { 14 | super(state); 15 | } 16 | 17 | @Override 18 | public boolean needToClearHistory() { return true; } 19 | 20 | @Override 21 | public int fixedArgumentsLength() { return 0; } 22 | 23 | public static void clearCanvas(Canvas c, DisplayState state) { 24 | Paint paint = new Paint(); 25 | paint.setColor(state.backColor); 26 | MainActivity.log( "clearing to "+state.backColor+ " "+c.getWidth()+" "+c.getHeight()); 27 | c.drawRect(-0.5f,-0.5f,max(state.width,state.height),max(state.width,state.height), paint); 28 | } 29 | @Override 30 | public void draw(Canvas c) { 31 | clearCanvas(c, state); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/Command.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import mobi.omegacentauri.vectordisplay.DisplayState; 4 | import mobi.omegacentauri.vectordisplay.MainActivity; 5 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 6 | 7 | import android.app.Activity; 8 | import android.graphics.Canvas; 9 | import android.os.Handler; 10 | import android.os.Message; 11 | import android.util.Log; 12 | 13 | public class Command { 14 | public DisplayState state; 15 | 16 | public boolean errorState; 17 | 18 | public Command(DisplayState state) { 19 | this.errorState = false; 20 | try { 21 | this.state = (DisplayState)state.clone(); 22 | } catch (CloneNotSupportedException e) { 23 | } 24 | } 25 | 26 | public boolean haveStringArgument() { return false; } 27 | 28 | public int fixedArgumentsLength() { return 0; } 29 | 30 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 31 | return state; 32 | } 33 | 34 | public DisplayState parse(Activity context, MyBuffer buffer) { 35 | if (!haveFullData(buffer)) 36 | return null; 37 | // DrawBitmap doesn't get checksummed, as small errors in bitmap transmission can 38 | // be ignored, hopefully. 39 | if (this.getClass()==DrawBitmap.class || buffer.checksum()) { 40 | return parseArguments(context, buffer); 41 | } 42 | else { 43 | Log.e( "VectorDisplay","bad checksum "+this.getClass()); 44 | errorState = true; 45 | return null; 46 | } 47 | } 48 | 49 | public boolean haveFullData(MyBuffer buffer) { 50 | if (! haveStringArgument()) { 51 | if (buffer.length < fixedArgumentsLength()+1) 52 | return false; 53 | } 54 | else { 55 | if (buffer.length <= fixedArgumentsLength()+1 || 56 | buffer.getByte(buffer.length-2) != 0) 57 | return false; 58 | } 59 | return true; 60 | } 61 | 62 | public void draw(Canvas c) { 63 | } 64 | 65 | public boolean doesDraw() { // does this type actually do anything when show() is called? 66 | return true; 67 | } 68 | 69 | public boolean needToClearHistory() { return false; } 70 | 71 | // returns true if we need to wait for onLayout() before proceeding with rendering 72 | public boolean handleCommand(Handler h) { return false; } 73 | 74 | static void sendAck(Handler h, byte command, long delay) { 75 | Message msg = h.obtainMessage(MainActivity.ACK); 76 | msg.arg1 = command; 77 | if (delay > 0) 78 | h.sendMessageDelayed(msg, delay); 79 | else 80 | h.sendMessage(msg); 81 | } 82 | 83 | static void sendAck(Handler h, byte command) { 84 | sendAck(h, command, 0); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/DeleteButton.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | 8 | import mobi.omegacentauri.vectordisplay.DisplayState; 9 | import mobi.omegacentauri.vectordisplay.MainActivity; 10 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 11 | 12 | public class DeleteButton extends Command { 13 | byte cmd; 14 | 15 | public DeleteButton(DisplayState state) { 16 | super(state); 17 | } 18 | 19 | @Override 20 | public int fixedArgumentsLength() { 21 | return 1; 22 | } 23 | 24 | @Override 25 | public boolean haveStringArgument() { 26 | return false; 27 | } 28 | 29 | @Override 30 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 31 | cmd = buffer.data[0]; 32 | return state; 33 | } 34 | 35 | @Override 36 | public boolean doesDraw() { // does this type actually do anything when show() is called? 37 | return false; 38 | } 39 | 40 | @Override 41 | public boolean handleCommand(Handler h) { 42 | Message msg = h.obtainMessage(MainActivity.DELETE_COMMAND); 43 | Bundle b = new Bundle(); 44 | b.putByte(MainActivity.KEY_COMMAND, cmd); 45 | msg.setData(b); 46 | h.sendMessage(msg); 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/DrawBitmap.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | 9 | import java.nio.IntBuffer; 10 | import java.nio.ShortBuffer; 11 | import java.util.Arrays; 12 | 13 | import mobi.omegacentauri.vectordisplay.DisplayState; 14 | import mobi.omegacentauri.vectordisplay.MainActivity; 15 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 16 | 17 | public class DrawBitmap extends Command { 18 | short x,y,w,h; 19 | int length; 20 | byte[] data; 21 | byte[] mask; 22 | static final byte MONOCHROME=1; 23 | static final byte GRAYSCALE=8; 24 | static final byte RGB565=16; 25 | static final byte RGB888=24; 26 | static final byte RGBA8888=32; 27 | static final int headerSize = 14; 28 | int foreColor; 29 | int backColor; 30 | byte depth; 31 | byte flags; 32 | static final byte FLAG_LOW_ENDIAN_BITS = 1; // lsbit on left 33 | static final byte FLAG_HAVE_MASK = 2; 34 | static final byte FLAG_PAD_BYTE = 4; 35 | static final byte FLAG_LOW_ENDIAN_BYTES = 8; // lsbyte first 36 | static final byte FLAG_IMAGE_FILE=16; // standard Android supported image file 37 | int neededLength = 4; 38 | 39 | private static Paint DefaultPaint() { 40 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 41 | p.setStyle(Paint.Style.STROKE); 42 | return p; 43 | } 44 | 45 | @Override 46 | public boolean haveFullData(MyBuffer buffer) { 47 | if (buffer.length < neededLength) 48 | return false; 49 | 50 | if (neededLength <= 4) { 51 | neededLength = buffer.getInt(0)+1+headerSize; 52 | } 53 | 54 | return buffer.length >= neededLength; 55 | } 56 | 57 | public DrawBitmap(DisplayState state) { 58 | super(state); 59 | } 60 | 61 | @Override 62 | public int fixedArgumentsLength() { 63 | return 6; 64 | } 65 | 66 | @Override 67 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 68 | depth = buffer.data[4]; 69 | flags = buffer.data[5]; 70 | x = buffer.getShort(6); 71 | y = buffer.getShort(8); 72 | 73 | int bitmapLength; 74 | int maskLength; 75 | 76 | MainActivity.log("flags = "+flags+" depth = "+depth); 77 | if ((flags & FLAG_IMAGE_FILE)==0) { 78 | w = buffer.getShort(10); 79 | h = buffer.getShort(12); 80 | MainActivity.log("width = "+w+" height = "+h); 81 | if (depth>=8) { 82 | bitmapLength = (depth/8) * w * h; 83 | } 84 | else { 85 | bitmapLength = ((flags & FLAG_PAD_BYTE) != 0) ? (w+7)/8*h : (w*h + 7) / 8; 86 | } 87 | 88 | if ((flags & FLAG_HAVE_MASK) != 0) { 89 | maskLength = (w*h + 7) / 8; 90 | } 91 | else { 92 | maskLength = 0; 93 | } 94 | 95 | } 96 | else { 97 | bitmapLength = buffer.getInt(10); 98 | maskLength = 0; 99 | } 100 | 101 | int bitmapOffset; 102 | if (depth == MONOCHROME) { 103 | foreColor = buffer.getInt(14); 104 | backColor = buffer.getInt(18); 105 | bitmapOffset = 22; 106 | } 107 | else { 108 | bitmapOffset = 14; 109 | } 110 | 111 | if (bitmapLength > 0) { 112 | data = Arrays.copyOfRange(buffer.data, bitmapOffset, bitmapOffset+bitmapLength); 113 | } 114 | else { 115 | data = null; 116 | } 117 | 118 | if (maskLength > 0) { 119 | mask = Arrays.copyOfRange(buffer.data, bitmapOffset + bitmapLength, bitmapOffset + bitmapLength+maskLength); 120 | } 121 | else { 122 | mask = null; 123 | } 124 | 125 | return state; 126 | } 127 | 128 | public Bitmap getArduinoBitmap() { 129 | Bitmap bitmap; 130 | 131 | boolean leBits = (flags & FLAG_LOW_ENDIAN_BITS) != 0; 132 | boolean leBytes = (flags & FLAG_LOW_ENDIAN_BYTES) != 0; 133 | 134 | if (mask == null && depth == RGB565) { 135 | short[] pixels = new short[ w*h ]; 136 | if (leBytes) 137 | for (int i = 0; i < pixels.length; i++) { 138 | pixels[i] = (short) ( (data[2*i] & 0xFF) | ((data[2*i+1] & 0xFF) << 8)); 139 | } 140 | else 141 | for (int i = 0; i < pixels.length; i++) { 142 | pixels[i] = (short) ((data[2*i] & 0xFF) | ((data[2*i+1] & 0xFF) << 8)); 143 | } 144 | bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); 145 | bitmap.copyPixelsFromBuffer(ShortBuffer.wrap(pixels)); 146 | } 147 | else { 148 | int[] pixels = new int[ w*h ]; 149 | if (depth == MONOCHROME) { 150 | if ((flags & FLAG_PAD_BYTE) == 0){ 151 | int offset = 0; 152 | if (leBits) 153 | for (int y = 0; y < h; y++) { 154 | int yw = y * w; 155 | for (int x = 0; x < w; x++) { 156 | if ((data[offset / 8] & (1 << (offset % 8))) != 0) 157 | pixels[yw + x] = foreColor; 158 | else 159 | pixels[yw + x] = backColor; 160 | offset++; 161 | } 162 | } 163 | else { 164 | for (int y = 0; y < h; y++) { 165 | int yw = y * w; 166 | for (int x = 0; x < w; x++) { 167 | if ((data[offset / 8] & (1 << (7 - offset % 8))) != 0) 168 | pixels[yw + x] = foreColor; 169 | else 170 | pixels[yw + x] = backColor; 171 | offset++; 172 | } 173 | } 174 | } 175 | } 176 | else { 177 | if (leBits) 178 | for (int y = 0; y < h; y++) { 179 | int yw = y * w; 180 | int yw1 = (w+7)/8 * w; 181 | for (int x = 0; x < w; x++) { 182 | if ((data[yw1 + x/8] & (1 << (x% 8))) != 0) 183 | pixels[yw + x] = foreColor; 184 | else 185 | pixels[yw + x] = backColor; 186 | } 187 | } 188 | else { 189 | for (int y = 0; y < h; y++) { 190 | int yw = y * w; 191 | int yw1 = (w+7)/8 * w; 192 | for (int x = 0; x < w; x++) { 193 | if ((data[yw1 + x/8] & (1 << (7 - x% 8))) != 0) 194 | pixels[yw + x] = foreColor; 195 | else 196 | pixels[yw + x] = backColor; 197 | } 198 | } 199 | } 200 | } 201 | } 202 | else if (depth == GRAYSCALE) { 203 | for (int i = 0; i < pixels.length; i++) { 204 | int g = data[i] & 0xFF; 205 | pixels[i] = 0xFF000000 | (g<<16) | (g<<8) | g; 206 | } 207 | } 208 | else if (depth == RGB888) { 209 | if (leBytes) 210 | for (int i = 0; i < pixels.length; i++) { 211 | int i3 = i*3; 212 | pixels[i] = 0xFF000000 | ((data[i3+2] & 0xFF) << 16) | ((data[i3+1] & 0xFF) << 8) | (data[i3] & 0xFF); 213 | } 214 | else 215 | for (int i = 0; i < pixels.length; i++) { 216 | int i3 = i*3; 217 | pixels[i] = 0xFF000000 | ((data[i3] & 0xFF) << 16) | ((data[i3+1] & 0xFF) << 8) | (data[i3+2] & 0xFF); 218 | } 219 | } 220 | else if (depth == RGB565) { 221 | if (leBytes) 222 | for (int i = 0; i < pixels.length; i++) { 223 | pixels[i] = rgb565to8888(data[2*i],data[2*i+1]); 224 | } 225 | else 226 | for (int i = 0; i < pixels.length; i++) { 227 | pixels[i] = rgb565to8888(data[2*i+1],data[2*i]); 228 | } 229 | } 230 | else if (depth == RGBA8888) { 231 | if (leBytes) 232 | for (int i = 0; i < pixels.length; i++) { 233 | int i4 = i*4; 234 | pixels[i] = ((data[i4+3] & 0xFF) << 24) | ((data[i4+2] & 0xFF) << 16) | ((data[i4+1] & 0xFF) << 8) | (data[i4] & 0xFF); 235 | } 236 | else 237 | for (int i = 0; i < pixels.length; i++) { 238 | int i4 = i*4; 239 | pixels[i] = ((data[i4] & 0xFF) << 24) | ((data[i4+1] & 0xFF) << 16) | ((data[i4+2] & 0xFF) << 8) | (data[i4+3] & 0xFF); 240 | } 241 | } 242 | else { 243 | return null; 244 | } 245 | 246 | if (mask != null) { 247 | if ((flags & FLAG_PAD_BYTE) == 0){ 248 | int offset = 0; 249 | if (leBits) 250 | for (int y = 0; y < h; y++) { 251 | int yw = y * w; 252 | for (int x = 0; x < w; x++) { 253 | if ((mask[offset / 8] & (1 << (offset % 8))) == 0) 254 | pixels[yw + x] = 0; 255 | offset++; 256 | } 257 | } 258 | else { 259 | for (int y = 0; y < h; y++) { 260 | int yw = y * w; 261 | for (int x = 0; x < w; x++) { 262 | if ((mask[offset / 8] & (1 << (7 - offset % 8))) == 0) 263 | pixels[yw + x] = 0; 264 | offset++; 265 | } 266 | } 267 | } 268 | } 269 | else { 270 | if (leBits) 271 | for (int y = 0; y < h; y++) { 272 | int yw = y * w; 273 | int yw1 = (w+7)/8 * w; 274 | for (int x = 0; x < w; x++) { 275 | if ((mask[yw1 + x/8] & (1 << (x% 8))) == 0) 276 | pixels[yw + x] = foreColor; 277 | } 278 | } 279 | else { 280 | for (int y = 0; y < h; y++) { 281 | int yw = y * w; 282 | int yw1 = (w+7)/8 * w; 283 | for (int x = 0; x < w; x++) { 284 | if ((mask[yw1 + x/8] & (1 << (7 - x% 8))) == 0) 285 | pixels[yw + x] = foreColor; 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 293 | bitmap.setPixels(pixels,0,w,0,0,w,h); 294 | MainActivity.log(" pixel 0 0 = "+pixels[0]); 295 | } 296 | 297 | return bitmap; 298 | } 299 | 300 | @Override 301 | public void draw(Canvas c) { 302 | if (data == null) 303 | return; 304 | 305 | Bitmap bitmap; 306 | if ((flags & FLAG_IMAGE_FILE) == 0) 307 | bitmap = getArduinoBitmap(); 308 | else 309 | bitmap = decodeBitmap(); 310 | 311 | if (bitmap != null) { 312 | MainActivity.log("drawing bitmap of size "+bitmap.getWidth()+" "+bitmap.getHeight()+" at "+x+" "+y); 313 | c.drawBitmap(bitmap, x, y, null); 314 | bitmap.recycle(); 315 | } 316 | } 317 | 318 | private Bitmap decodeBitmap() { 319 | try { 320 | return BitmapFactory.decodeByteArray(data, 0, data.length); 321 | } 322 | catch (Exception e) { 323 | return null; 324 | } 325 | } 326 | 327 | static private int rgb565to8888(byte low, byte high) { 328 | int c = (low&0xFF) | ((high&0xFF)<<8); 329 | return 0xFF000000 | ((((c>>11) & 0x1F) * 255 / 0x1F) << 16) | ((((c>>5) & 0x3F) * 255 / 0x3F) << 8) | ((c & 0x1F) * 255 / 0x1F); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/FillCircle.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | 7 | import mobi.omegacentauri.vectordisplay.DisplayState; 8 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 9 | 10 | public class FillCircle extends Command { 11 | short x,y,r; 12 | static Paint p = DefaultPaint(); 13 | 14 | private static Paint DefaultPaint() { 15 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 16 | p.setStyle(Paint.Style.FILL); 17 | p.setStrokeWidth(0); 18 | return p; 19 | } 20 | 21 | public FillCircle(DisplayState state) { 22 | super(state); 23 | } 24 | 25 | @Override 26 | public int fixedArgumentsLength() { 27 | return 6; 28 | } 29 | 30 | @Override 31 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 32 | x = buffer.getShort(0); 33 | y = buffer.getShort(2); 34 | r = buffer.getShort(4); 35 | return state; 36 | } 37 | 38 | @Override 39 | public void draw(Canvas c) { 40 | p.setColor(state.foreColor); 41 | 42 | c.drawCircle(x, y, r, p); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/mobi/omegacentauri/vectordisplay/commands/FillPoly.java: -------------------------------------------------------------------------------- 1 | package mobi.omegacentauri.vectordisplay.commands; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | 8 | import mobi.omegacentauri.vectordisplay.DisplayState; 9 | import mobi.omegacentauri.vectordisplay.VectorAPI.MyBuffer; 10 | 11 | public class FillPoly extends Command { 12 | int n; 13 | short[] x; 14 | short[] y; 15 | int pos; 16 | static Paint p = DefaultPaint(); 17 | static Path poly = new Path(); 18 | int neededLength = 2; 19 | 20 | private static Paint DefaultPaint() { 21 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 22 | p.setStyle(Paint.Style.FILL); 23 | return p; 24 | } 25 | 26 | public FillPoly(DisplayState state) { 27 | super(state); 28 | } 29 | 30 | @Override 31 | public boolean haveFullData(MyBuffer buffer) { 32 | if (buffer.length < neededLength) 33 | return false; 34 | int n = 0xFFFF & buffer.getShort(0); 35 | neededLength = 2+n*4+1; 36 | return buffer.length >= neededLength; 37 | } 38 | 39 | @Override 40 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 41 | n = buffer.getShort(0); 42 | x = new short[n]; 43 | y = new short[n]; 44 | for (int i=0;i= neededLength; 36 | } 37 | 38 | @Override 39 | public DisplayState parseArguments(Activity context, MyBuffer buffer) { 40 | n = buffer.getShort(0); 41 | x = new short[n]; 42 | y = new short[n]; 43 | for (int i=0;i 8 | 9 | 16 | 21 | 22 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 13 | 16 | 18 | 20 | 22 | 24 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpruss/VectorDisplay/286bf571c13b825e34b3911e5875d997b8519454/app/src/main/res/mipmap/icon.png -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4 5 | 10 6 | 20 7 | 25 8 | 30 9 | 40 10 | 50 11 | 60 12 | 13 | 14 | USB 15 | WiFi 16 | Bluetooth 17 | 18 | -------------------------------------------------------------------------------- /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 | VectorDisplay 3 | Settings 4 | Synchronous Serial Port 5 | 6 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | maven { 6 | url 'https://maven.google.com/' 7 | name 'Google' 8 | } 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:2.3.3' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | maven { 19 | url 'https://maven.google.com/' 20 | name 'Google' 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpruss/VectorDisplay/286bf571c13b825e34b3911e5875d997b8519454/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun May 20 10:15:54 CDT 2018 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-4.4-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 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 41 | 43 | 45 | 53 | 54 | 56 | 64 | 65 | 67 | 75 | 76 | 78 | 86 | 87 | 89 | 97 | 98 | 100 | 108 | 109 | 111 | 119 | 120 | 121 | 123 | 124 | 126 | image/svg+xml 127 | 129 | 130 | 131 | 132 | 133 | 138 | 146 | 154 | 162 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /import-summary.txt: -------------------------------------------------------------------------------- 1 | ECLIPSE ANDROID PROJECT IMPORT SUMMARY 2 | ====================================== 3 | 4 | Ignored Files: 5 | -------------- 6 | The following files were *not* copied into the new Gradle project; you 7 | should evaluate whether these are still needed in your project and if 8 | so manually move them: 9 | 10 | * License.txt 11 | * proguard.cfg 12 | 13 | Moved Files: 14 | ------------ 15 | Android Gradle projects use a different directory structure than ADT 16 | Eclipse projects. Here's how the projects were restructured: 17 | 18 | * AndroidManifest.xml => app\src\main\AndroidManifest.xml 19 | * assets\ => app\src\main\assets 20 | * lint.xml => app\lint.xml 21 | * res\ => app\src\main\res\ 22 | * src\ => app\src\main\java\ 23 | 24 | Next Steps: 25 | ----------- 26 | You can now build the project. The Gradle project needs network 27 | connectivity to download dependencies. 28 | 29 | Bugs: 30 | ----- 31 | If for some reason your project does not build, and you determine that 32 | it is due to a bug or limitation of the Eclipse to Gradle importer, 33 | please file a bug at http://b.android.com with category 34 | Component-Tools. 35 | 36 | (This import summary is for your information only, and can be deleted 37 | after import once you are satisfied with the results.) 38 | -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Sat May 26 20:51:46 CDT 2018 8 | ndk.dir=C\:\\Users\\alexander_pruss\\AppData\\Local\\Android\\Sdk\\ndk-bundle 9 | sdk.dir=C\:\\Users\\alexander_pruss\\AppData\\Local\\Android\\Sdk 10 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':usbserial-4.5.2-release' 2 | -------------------------------------------------------------------------------- /test/arc.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | import math 5 | from vectordisplay import VectorDisplay 6 | from random import randint 7 | from time import sleep 8 | 9 | if len(sys.argv)>1: 10 | addr = sys.argv[1] 11 | else: 12 | addr = '192.168.1.110' 13 | 14 | port = 7788 15 | 16 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | s.connect((addr, port)) 18 | print("connected") 19 | 20 | w = 300 21 | h = 300 22 | v = VectorDisplay(s.sendall,lowendian=False) 23 | v.initialize(w,h) 24 | v.arc(100,100,50,45,135) 25 | v.arc(100,100,50,180+45,180+135,fill=True) 26 | sleep(0.5) # needed on Windows 27 | s.close() 28 | -------------------------------------------------------------------------------- /test/bitmap.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | import math 5 | from vectordisplay import VectorDisplay 6 | from random import randint 7 | from time import sleep 8 | from bitmap_data import * 9 | 10 | if len(sys.argv)>1: 11 | addr = sys.argv[1] 12 | else: 13 | addr = '192.168.1.110' 14 | 15 | port = 7788 16 | 17 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | s.connect((addr, port)) 19 | print("connected") 20 | 21 | w = 420 22 | h = 420 23 | v = VectorDisplay(s.sendall,lowendian=False) 24 | v.initialize(w,h) 25 | v.bitmap(10,10,dimensions[0],dimensions[1],bw,foreColor=0x7f007F00,backColor=0x7f1f1f1f,depth=1) 26 | v.bitmap(80,80,dimensions[0],dimensions[1],gray,depth=8) 27 | v.bitmap(160,160,dimensions[0],dimensions[1],rgb888,depth=24) 28 | v.bitmap(220,220,dimensions[0],dimensions[1],rgb8888,depth=32) 29 | v.bitmap(300,300,dimensions[0],dimensions[1],rgb565,depth=16) 30 | sleep(0.5) # needed on Windows 31 | s.close() -------------------------------------------------------------------------------- /test/helper.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | import math 5 | from vectordisplay import VectorDisplay 6 | from random import randint 7 | from time import sleep 8 | 9 | if len(sys.argv)>1: 10 | addr = sys.argv[1] 11 | else: 12 | addr = '192.168.1.110' 13 | 14 | port = 7788 15 | 16 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | s.connect((addr, port)) 18 | print("connected") 19 | 20 | w = 160 21 | h = 160 22 | v = VectorDisplay(s.sendall,lowendian=False) 23 | v.initialize(w,h) 24 | v.rounded(True) 25 | v.foreColor(0xFFFF0000) 26 | v.fillCircleHelper(60,55,50,2,50) 27 | v.foreColor(0xFF00FF00) 28 | v.fillCircleHelper(80,55,50,1,50) 29 | sleep(0.5) # needed on Windows 30 | s.close() -------------------------------------------------------------------------------- /test/nettest.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | from vectordisplay import VectorDisplay 5 | from threading import Thread 6 | from random import randint 7 | from time import sleep 8 | 9 | if len(sys.argv)>1: 10 | addr = sys.argv[1] 11 | else: 12 | addr = '192.168.1.110' 13 | 14 | port = 7788 15 | 16 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | s.connect((addr, port)) 18 | print("connected") 19 | 20 | def dec16(a,b): 21 | return (a<<8) | (b&0xFF); 22 | 23 | def reader(): 24 | while 1: 25 | try: 26 | data = s.recv(8) 27 | print(" ".join("%02X" % b for b in data)) 28 | c = chr(data[0]) 29 | if c=='U' or c=='D' or c=='M': 30 | print ( chr(data[0])+":%d,%d" % (dec16(data[2],data[3]),dec16(data[4],data[5])) ) 31 | except: 32 | pass 33 | 34 | thread = Thread(target = reader, args = ()) 35 | thread.start() 36 | 37 | out = VectorDisplay(s.sendall,lowendian=False) 38 | 39 | out.initialize(240,320) 40 | 41 | sleep(1) 42 | while 1: 43 | x1 = randint(0,239) 44 | y1 = randint(0,319) 45 | x2 = randint(0,239) 46 | y2 = randint(0,319) 47 | out.line(x1,y1,x2,y2) 48 | sleep(1) 49 | -------------------------------------------------------------------------------- /test/poly.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | import math 5 | from vectordisplay import VectorDisplay 6 | from random import randint 7 | from time import sleep 8 | 9 | if len(sys.argv)>1: 10 | addr = sys.argv[1] 11 | else: 12 | addr = '192.168.1.110' 13 | 14 | port = 7788 15 | 16 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | s.connect((addr, port)) 18 | print("connected") 19 | 20 | w = 300 21 | h = 400 22 | v = VectorDisplay(s.sendall,lowendian=False) 23 | v.initialize(w,h) 24 | v.rounded(True) 25 | v.poly([(100,100),(200,100),(150,50)], fill=True) 26 | sleep(0.5) # needed on Windows 27 | s.close() 28 | -------------------------------------------------------------------------------- /test/rects.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | from vectordisplay import VectorDisplay 5 | from random import randint 6 | from time import sleep 7 | 8 | if len(sys.argv)>1: 9 | addr = sys.argv[1] 10 | else: 11 | addr = '192.168.1.110' 12 | 13 | port = 7788 14 | 15 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 | s.connect((addr, port)) 17 | print("connected") 18 | 19 | w = 3 20 | h = 4 21 | v = VectorDisplay(s.sendall,lowendian=False) 22 | v.initialize(w,h) 23 | v.rounded(True) 24 | v.foreColor(0xFFFF0000) 25 | v.rectangle(0,0,w-1,h-1) 26 | v.foreColor(0x3F00FF00) 27 | v.fillRectangle(0,0,w-1,h-1) 28 | v.foreColor(0x7F0000FF) 29 | v.fillRectangle(0,0,0,0) 30 | v.foreColor(0x7f00007F) 31 | v.fillRectangle(1,0,1,0) 32 | sleep(0.5) # needed on Windows 33 | s.close() -------------------------------------------------------------------------------- /test/roundedrects.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | from vectordisplay import VectorDisplay 5 | from random import randint 6 | from time import sleep 7 | 8 | if len(sys.argv)>1: 9 | addr = sys.argv[1] 10 | else: 11 | addr = '192.168.1.110' 12 | 13 | port = 7788 14 | 15 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 | s.connect((addr, port)) 17 | print("connected") 18 | 19 | v = VectorDisplay(s.sendall,lowendian=True) 20 | v.initialize() # 200,200) 21 | v.coordinates(200,200); 22 | v.foreColor(0xFFFF0000) 23 | v.roundedRectangle(10,10,100,100,30,fill=False) 24 | v.foreColor(0x3F00FF00) 25 | v.roundedRectangle(80,80,180,180,30,fill=True) 26 | sleep(0.5) # needed on Windows 27 | s.close() -------------------------------------------------------------------------------- /test/serialtest.py: -------------------------------------------------------------------------------- 1 | import serial 2 | from random import randint 3 | from time import sleep 4 | import sys 5 | 6 | if len(sys.argv)>1: 7 | addr = sys.argv[1] 8 | else: 9 | addr = 'COM29' 10 | 11 | s = serial.Serial(addr,baudrate=115200) 12 | print("connected") 13 | 14 | def sendCommand(c, args): 15 | command = [ ord(c), ord(c)^0xff ] + args + [ 0xff^sum(bytearray(args)) ] 16 | s.write(bytearray(c&0xFF for c in command)) 17 | s.flush() 18 | 19 | ack = False 20 | 21 | while not ack: 22 | print("Init") 23 | sendCommand('H', [0x12, 0x34, 0, 0]) # big endian 24 | while s.inWaiting()>0: 25 | r = s.read() 26 | if b'A' == r: 27 | ack = True 28 | break 29 | if not ack: 30 | sleep(1) 31 | print("Ready") 32 | while 1: 33 | x1 = randint(0,239) 34 | y1 = randint(0,319) 35 | x2 = randint(0,239) 36 | y2 = randint(0,319) 37 | sendCommand('L', [x1>>8,x1&0xFF, y1>>8,y1&0xFF, x2>>8,x2&0xff, y2>>8,y2&0xFF ]) 38 | sleep(0.1) 39 | -------------------------------------------------------------------------------- /test/text.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import os 4 | import math 5 | from vectordisplay import VectorDisplay 6 | from random import randint 7 | from time import sleep 8 | from bitmap_data import * 9 | 10 | if len(sys.argv)>1: 11 | addr = sys.argv[1] 12 | else: 13 | addr = '192.168.1.143' 14 | 15 | port = 7788 16 | 17 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | s.connect((addr, port)) 19 | print("connected") 20 | 21 | w = 100 22 | h = 100 23 | v = VectorDisplay(s.sendall,lowendian=False) 24 | v.initialize(w,h) 25 | v.text(0,0,"Hello, world!") 26 | v.font(8|16); # bold italic serif 27 | v.text(0,8,"Hello, world!") 28 | sleep(0.5) # needed on Windows 29 | s.close() 30 | -------------------------------------------------------------------------------- /test/vectordisplay.py: -------------------------------------------------------------------------------- 1 | from math import floor 2 | 3 | class VectorDisplay(object): 4 | def __init__(self,writer,lowendian=True): 5 | self.write = writer 6 | if lowendian: 7 | self.e16 = self.e16le 8 | self.e32 = self.e32le 9 | else: 10 | self.e16 = self.e16be 11 | self.e32 = self.e32be 12 | 13 | def e16le(self, x): 14 | x = int(floor(x)) 15 | return (x&0xFF,(x>>8)&0xFF) 16 | def e16be(self, x): 17 | x = int(floor(x)) 18 | return ((x>>8)&0xFF,x&0xFF) 19 | def e32le(self, x): 20 | x = int(floor(x)) 21 | return (x&0xFF,(x>>8)&0xFF,(x>>16)&0xFF,(x>>24)&0xFF) 22 | def e32be(self, x): 23 | x = int(floor(x)) 24 | return ((x>>24)&0xFF, (x>>16)&0xFF, (x>>8)&0xFF, x&0xFF) 25 | 26 | def command(self,c,data): 27 | bc = ord(c) 28 | self.write(bytearray((bc,bc^0xFF)+data+((0xFF&sum(data))^0xFF,))) 29 | 30 | def initialize(self,width=240,height=320): 31 | self.command('Z',self.e16(0x1234)+self.e16(width)+self.e16(height)+self.e32(65536)+self.e16(0)*3) 32 | 33 | def line(self,x1,y1,x2,y2): 34 | self.command('L',self.e16(x1)+self.e16(y1)+self.e16(x2)+self.e16(y2)) 35 | 36 | def attr8(self,attr,value): 37 | self.command('Y',(ord(attr),value)) 38 | 39 | def attr16(self,attr,data): 40 | self.command('A',(ord(attr),)+data) 41 | 42 | def attr32(self,attr,data): 43 | self.command('B',(ord(attr),)+data) 44 | 45 | def fp32(self,x): 46 | return int(x*65536.+0.5) 47 | 48 | def thickness(self,x): 49 | self.attr32('t',self.e32(self.fp32(x))) 50 | 51 | def fillRectangle(self,x1,y1,x2,y2): 52 | self.command('R', self.e16(x1)+self.e16(y1)+self.e16(x2)+self.e16(y2)) 53 | 54 | def coordinates(self,w,h): 55 | self.attr32('c',self.e16(w)+self.e16(h)) 56 | 57 | def rounded(self,r): 58 | self.attr8('n',r) 59 | 60 | def font(self,f): 61 | self.attr8('f',f) 62 | 63 | def rectangle(self,x1,y1,x2,y2,fill=False): 64 | if fill: 65 | self.command('R', self.e16(x1)+self.e16(y1)+self.e16(x2)+self.e16(y2)) 66 | else: 67 | self.line(x1,y1,x2,y1) 68 | self.line(x2,y1,x2,y2) 69 | self.line(x2,y2,x1,y2) 70 | self.line(x1,y2,x1,y1) 71 | 72 | def text(self,x,y,s): 73 | self.command('T', self.e16(x)+self.e16(y)+tuple(ord(x) for x in s)+(0,)) 74 | 75 | def roundedRectangle(self,x1,y1,x2,y2,r,fill=False): 76 | self.command('Q', self.e16(x1)+self.e16(y1)+self.e16(x2)+self.e16(y2)+self.e16(r)+(1 if fill else 0,)) 77 | 78 | def foreColor(self,c): 79 | self.attr32('f',self.e32(c)) 80 | 81 | def point(self,x,y): 82 | self.command('P',self.e16(x)+self.e16(y)) 83 | 84 | def arc(self,cx,cy,r,angle1,angle2,fill=False): 85 | self.command('S',self.e16(cx)+self.e16(cy)+self.e16(r)+self.e32(angle1*65536)+self.e32(angle2*65536)+(1 if fill else 0,)) 86 | 87 | def poly(self,points,fill=True): 88 | path = self.e16(len(points)) 89 | for p in points: 90 | path += self.e16(p[0])+self.e16(p[1]) 91 | self.command('N' if fill else 'O',path) 92 | 93 | def bitmap(self,x,y,width,height,data,depth=1,foreColor=0xFFFFFFFF,backColor=0xFF000000): 94 | if depth==1: 95 | dataLen = (width*height+7)//8 96 | outData = ( self.e32(8+dataLen)+(depth,0)+self.e16(x)+self.e16(y)+self.e16(width)+self.e16(height)+ 97 | self.e32(foreColor)+self.e32(backColor)+data[:dataLen] ) 98 | elif depth>=8: 99 | dataLen = width*height*(depth//8) 100 | outData = ( self.e32(dataLen)+(depth,8)+self.e16(x)+self.e16(y)+self.e16(width)+self.e16(height)+ 101 | data[:dataLen] ) 102 | else: 103 | return 104 | 105 | self.command('K',outData) 106 | 107 | def fillCircleHelper(self,cx,cy,r,corners,delta): 108 | # this will be ugly if alpha < 1.0 in the color 109 | if corners & 3: 110 | self.line(cx,cy-r,cx,cy+r+delta) 111 | 112 | if corners & 2: 113 | self.arc(cx,cy,r,90,180,fill=False) 114 | self.arc(cx,cy,r,90,180,fill=True) 115 | self.arc(cx,cy+delta,r,90,180,fill=False) 116 | self.arc(cx,cy+delta,r,90,180,fill=True) 117 | if delta>0: 118 | self.rectangle(cx-r,cy,cx,cy+delta,fill=False) 119 | self.rectangle(cx-r,cy,cx,cy+delta,fill=True) 120 | if corners & 1: 121 | self.arc(cx,cy,r,270,180,fill=False) 122 | self.arc(cx,cy,r,270,180,fill=True) 123 | self.arc(cx,cy+delta,r,270,180,fill=False) 124 | self.arc(cx,cy+delta,r,270,180,fill=True) 125 | if delta>0: 126 | self.rectangle(cx,cy,cx+r,cy+delta,fill=False) 127 | self.rectangle(cx,cy,cx+r,cy+delta,fill=True) 128 | -------------------------------------------------------------------------------- /usbserial-4.5.2-release/build.gradle: -------------------------------------------------------------------------------- 1 | configurations.maybeCreate("default") 2 | artifacts.add("default", file('usbserial-4.5.2-release.aar')) -------------------------------------------------------------------------------- /usbserial-4.5.2-release/usbserial-4.5.2-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpruss/VectorDisplay/286bf571c13b825e34b3911e5875d997b8519454/usbserial-4.5.2-release/usbserial-4.5.2-release.aar -------------------------------------------------------------------------------- /usbserial-4.5.2-release/usbserial-4.5.2-release.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /vectordisplay.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------