├── LICENSE ├── README.rst └── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── geospark │ └── scoperoid │ └── ApplicationTest.java └── main ├── AndroidManifest.xml ├── java └── com │ └── geospark │ └── scoperoid │ ├── MainActivity.java │ ├── Scope.java │ ├── ViewAspectRatioMeasurer.java │ ├── WaveformGrid.java │ ├── WaveformLine.java │ ├── WaveformRenderer.java │ └── WaveformView.java └── res ├── drawable ├── button.9.png ├── grid.png └── rigol_logo.png ├── layout └── activity_main.xml ├── menu └── menu_main.xml ├── mipmap-hdpi └── ic_launcher.png ├── mipmap-mdpi └── ic_launcher.png ├── mipmap-xhdpi └── ic_launcher.png ├── mipmap-xxhdpi └── ic_launcher.png ├── values-w820dp └── dimens.xml ├── values ├── attrs.xml ├── colors.xml ├── dimens.xml ├── strings.xml └── styles.xml └── xml └── device_filter.xml /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 GeoSpark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Scoperoid 2 | ========= 3 | A simple second monitor for Rigol MSO1000Z/DS1000Z-series digital oscilloscopes and Android devices. 4 | 5 | Caveats 6 | ------- 7 | * I AM NOT RESPONSIBLE IF THIS BREAKS YOUR ANDROID DEVICE OR YOUR TEST EQUIPMENT, SENDS IT OUT OF 8 | CALIBRATION, VOIDS THE WARRANTY, OR RELEASES THE MAGIC BLUE SMOKE. IT IS BEING PROVIDED AS-IS. 9 | * This has only been tested on a Rigol DS1054Z and an Android Nexus 6 running Lollipop 5.0.1 and 5.1 10 | * It requires a "USB-on-the-go" adapter to enable the Android device to act as host, and this 11 | should ultimately plug in to the USB port on the *back* of the 'scope. 12 | * Currently it only detects the 'scope when it is plugged in *after* the app has been launched. 13 | * Due to a bug in Android (or maybe a misunderstanding on my part), the app will ask you for 14 | permission to access the 'scope every time you plug it in, regardless of whether you check the 15 | "remember permission" box. 16 | * Only channel 1 is supported at the moment, but other channels are easy to add. 17 | * It seems there is no way to determine the RUN/STOP mode the 'scope is currently in, so the 18 | RUN/STOP button on the phone provides no feedback, merely sends the command to the 'scope and 19 | assumes the 'scope is in RUN mode when the app is started. 20 | * Thanks to the eagle-eyed PeDre on the EEVBlog forum, it *is* possible to get 21 | the RUN/STOP status by calling `:TRIGger:STATus?` So expect this in an 22 | upcoming commit. 23 | 24 | Notes and stuff 25 | --------------- 26 | I have tinkered with the Ethernet interface to my scope and come up with this Python script to test it: 27 | https://gist.github.com/MerseyViking/c67b7d6ebdda55929fbd 28 | 29 | Seems fairly straightforward, so implementing it on Android should be a breeze. 30 | 31 | TODO 32 | ---- 33 | * Implement remaining channels. 34 | * Add channel statistics as selected from the left-side buttons. 35 | * Detect the presence of the 'scope when the app is started. 36 | * Add support for other 'scopes that use the USBTMC standard. Anyone with a bunch of 'scopes and 37 | a desire to write some Android code? 38 | * Test on a wider variety of phones. 39 | * See if reasonable throughput can be achieved via a USB Bluetooth dongle. 40 | 41 | What I may do 42 | ------------- 43 | It might be quite fun to record waveform data on the Android device, but as we can't gurantee we 44 | get every part of the waveform due to USB bandwith and CPU speed, and there appears to be no way of 45 | getting an absolute timestamp, this might only be useful as a series of frames rather than a 46 | continuous signal. Maybe in rolling mode we could get something suitable for playback, but the 47 | timebase for that has to be quite large so there will be signal bandwidth limits. 48 | We could trigger the 'scope's record/playback mode and maybe suck out the data via that interface, 49 | but I've not looked at that. 50 | 51 | What I am unlikely to do 52 | ------------------------ 53 | This app was developed for fun, and so I can see the waveform without moving my head much when 54 | probing hard-to-reach and fiddly pins. I have no intention of turning it into a full interface 55 | to the 'scope unless someone pays me to do it. Of course, feel free to fork and enhance the app 56 | if you want. 57 | 58 | This code is released under the MIT license, so you can pretty much do what you like with it, but I 59 | would like to reserve the right to put it on the Play Store at some point in the future. If you 60 | have a burning issue with this, let me know and we can sort something out. 61 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.1.2" 6 | 7 | defaultConfig { 8 | applicationId "com.geospark.scoperoid" 9 | minSdkVersion 18 10 | targetSdkVersion 21 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:22.0.0' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/merseyviking/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/geospark/scoperoid/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 GeoSpark 2 | // 3 | // Released under the MIT License (MIT) 4 | // See the LICENSE file, or visit http://opensource.org/licenses/MIT 5 | 6 | package com.geospark.scoperoid; 7 | 8 | import android.app.Application; 9 | import android.test.ApplicationTestCase; 10 | 11 | /** 12 | * Testing Fundamentals 13 | */ 14 | public class ApplicationTest extends ApplicationTestCase { 15 | public ApplicationTest() { 16 | super(Application.class); 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/geospark/scoperoid/MainActivity.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 GeoSpark 2 | // 3 | // Released under the MIT License (MIT) 4 | // See the LICENSE file, or visit http://opensource.org/licenses/MIT 5 | 6 | package com.geospark.scoperoid; 7 | 8 | import android.app.PendingIntent; 9 | import android.content.BroadcastReceiver; 10 | import android.content.Context; 11 | import android.content.Intent; 12 | import android.content.IntentFilter; 13 | import android.content.pm.ActivityInfo; 14 | import android.hardware.usb.UsbDevice; 15 | import android.hardware.usb.UsbManager; 16 | import android.support.v7.app.ActionBarActivity; 17 | import android.os.Bundle; 18 | import android.util.Log; 19 | import android.view.Menu; 20 | import android.view.MenuItem; 21 | import android.view.View; 22 | import android.widget.TextView; 23 | 24 | import java.math.BigDecimal; 25 | 26 | 27 | public class MainActivity extends ActionBarActivity implements Scope.ScopeCallback { 28 | private static final String ACTION_USB_PERMISSION = "com.geospark.scoperoid.USB_PERMISSION"; 29 | private static final String TAG = "MAIN"; 30 | 31 | private static final BigDecimal NANO = new BigDecimal("0.000000001"); 32 | private static final BigDecimal MICRO = new BigDecimal("0.000001"); 33 | private static final BigDecimal MILLI = new BigDecimal("0.001"); 34 | private static final BigDecimal TIMEBASE_SCALAR = new BigDecimal("100"); 35 | private static final BigDecimal VERTICAL_SCALE_SCALAR = new BigDecimal("25"); 36 | private static final BigDecimal HORIZONTAL_DIVISIONS = new BigDecimal("6"); 37 | 38 | private UsbManager mUsbManager; 39 | private WaveformView waveformView; 40 | private Scope _scope; 41 | 42 | private PendingIntent mPermissionIntent = null; 43 | 44 | private boolean _scopeRunning = true; 45 | 46 | private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { 47 | public void onReceive(Context context, Intent intent) { 48 | String action = intent.getAction(); 49 | if (ACTION_USB_PERMISSION.equals(action)) { 50 | synchronized (this) { 51 | UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 52 | 53 | if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { 54 | int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; 55 | View decorView = getWindow().getDecorView(); 56 | decorView.setSystemUiVisibility(uiOptions); 57 | 58 | if (device != null) { 59 | _scope.connectUSB(mUsbManager, device); 60 | _scope.postCommand(Scope.WAV_SOURCE, "CHAN1"); 61 | _scope.postCommand(Scope.WAV_MODE, "NORM"); 62 | _scope.postCommand(Scope.WAV_FORMAT, "BYTE"); 63 | _scope.postCommand(Scope.WAV_DATA_Q); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | }; 70 | 71 | private final BroadcastReceiver mUsbConnectedReceiver = new BroadcastReceiver() { 72 | public void onReceive(Context context, Intent intent) { 73 | String action = intent.getAction(); 74 | 75 | if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { 76 | synchronized (this) { 77 | UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 78 | 79 | if (device != null && device.getVendorId() == 6833 && device.getProductId() == 1230) { 80 | if (!mUsbManager.hasPermission(device)) { 81 | mUsbManager.requestPermission(device, mPermissionIntent); 82 | } 83 | } 84 | } 85 | } 86 | 87 | if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { 88 | synchronized (this) { 89 | UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 90 | if (device != null && device.getVendorId() == 6833 && device.getProductId() == 1230) { 91 | _scope.disconnectUSB(); 92 | } 93 | } 94 | } 95 | } 96 | }; 97 | 98 | @Override 99 | protected void onPause() { 100 | waveformView.onPause(); 101 | super.onPause(); 102 | } 103 | 104 | @Override 105 | protected void onResume() { 106 | super.onResume(); 107 | waveformView.onResume(); 108 | } 109 | 110 | @Override 111 | protected void onCreate(Bundle savedInstanceState) { 112 | super.onCreate(savedInstanceState); 113 | setContentView(R.layout.activity_main); 114 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 115 | int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; 116 | View decorView = getWindow().getDecorView(); 117 | decorView.setSystemUiVisibility(uiOptions); 118 | 119 | waveformView = (WaveformView) findViewById(R.id.waveformView); 120 | 121 | _scope = new Scope(); 122 | _scope.register(this); 123 | 124 | mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 125 | 126 | // Intent intent = getIntent(); 127 | // UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 128 | // 129 | // if (device != null) { 130 | // connectUSB(device); 131 | // _refresh_handler.post(_refresh_runnable); 132 | // } else { 133 | registerReceiver(mUsbConnectedReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED)); 134 | registerReceiver(mUsbConnectedReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED)); 135 | 136 | mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); 137 | registerReceiver(mUsbReceiver, new IntentFilter(ACTION_USB_PERMISSION)); 138 | // } 139 | } 140 | 141 | @Override 142 | protected void onDestroy() { 143 | _scope.unregister(); 144 | unregisterReceiver(mUsbConnectedReceiver); 145 | unregisterReceiver(mUsbReceiver); 146 | super.onDestroy(); 147 | } 148 | 149 | @Override 150 | public boolean onCreateOptionsMenu(Menu menu) { 151 | // Inflate the menu; this adds items to the action bar if it is present. 152 | getMenuInflater().inflate(R.menu.menu_main, menu); 153 | return true; 154 | } 155 | 156 | @Override 157 | public boolean onOptionsItemSelected(MenuItem item) { 158 | // Handle action bar item clicks here. The action bar will 159 | // automatically handle clicks on the Home/Up button, so long 160 | // as you specify a parent activity in AndroidManifest.xml. 161 | int id = item.getItemId(); 162 | 163 | //noinspection SimplifiableIfStatement 164 | if (id == R.id.action_settings) { 165 | return true; 166 | } 167 | 168 | return super.onOptionsItemSelected(item); 169 | } 170 | 171 | public void onStartStopButton(View v) { 172 | // We appear to have no way of determining the run/stop state of the scope, so we'll have to assume it's running when we start the app. 173 | if (_scope != null) { 174 | if (_scopeRunning) { 175 | _scope.postCommand(Scope.STOP); 176 | } else { 177 | _scope.postCommand(Scope.RUN); 178 | } 179 | _scopeRunning = !_scopeRunning; 180 | } 181 | } 182 | 183 | @Override 184 | public void result(String command, byte[] data) { 185 | if (Scope.WAV_DATA_Q.equals(command) && data.length > 11) { 186 | waveformView.setWaveformData(data); 187 | _scope.postCommand(Scope.WAV_PREAMBLE_Q); 188 | _scope.postCommand(Scope.WAV_DATA_Q); 189 | } else if (Scope.WAV_PREAMBLE_Q.equals(command)) { 190 | try { 191 | String sdata = new String(data); 192 | String[] params = sdata.split(","); 193 | 194 | // The documentation says that YIncrement is the vertical scale divided by 25, so we factor that out. The lower limit is 5mV. 195 | BigDecimal vscale = new BigDecimal(params[Scope.WAV_PREAMBLE_YINCREMENT]).setScale(4, BigDecimal.ROUND_HALF_UP).multiply(VERTICAL_SCALE_SCALAR); 196 | TextView vscaleView = (TextView) findViewById(R.id.verticalScale); 197 | 198 | if (vscale.compareTo(BigDecimal.ONE) < 0.0) { 199 | String s = String.format(getString(R.string.vscale), vscale.scaleByPowerOfTen(3), getString(R.string.millivolts)); 200 | vscaleView.setText(s); 201 | } else { 202 | String s = String.format(getString(R.string.vscale), vscale, getString(R.string.volts)); 203 | vscaleView.setText(s); 204 | } 205 | 206 | // The documentation says that the XIncrement parameter is the timescale divided by 100, so we factor that out. The lower 207 | // limit is 5ns. 208 | BigDecimal hscale = new BigDecimal(params[Scope.WAV_PREAMBLE_XINCREMENT]).setScale(11, BigDecimal.ROUND_HALF_UP).multiply(TIMEBASE_SCALAR); 209 | TextView hscaleView = (TextView) findViewById(R.id.timebase); 210 | 211 | if (hscale.compareTo(MICRO) < 0) { 212 | String s = String.format(getString(R.string.timebase), hscale.scaleByPowerOfTen(9), getString(R.string.nanoseconds)); 213 | hscaleView.setText(s); 214 | } else if (hscale.compareTo(MILLI) < 0) { 215 | String s = String.format(getString(R.string.timebase), hscale.scaleByPowerOfTen(6), getString(R.string.microseconds)); 216 | hscaleView.setText(s); 217 | } else if (hscale.compareTo(BigDecimal.ONE) < 0) { 218 | String s = String.format(getString(R.string.timebase), hscale.scaleByPowerOfTen(3), getString(R.string.milliseconds)); 219 | hscaleView.setText(s); 220 | } else { 221 | String s = String.format(getString(R.string.timebase), hscale, getString(R.string.seconds)); 222 | hscaleView.setText(s); 223 | } 224 | 225 | BigDecimal XStart = hscale.multiply(HORIZONTAL_DIVISIONS); 226 | BigDecimal hoffset = new BigDecimal(params[Scope.WAV_PREAMBLE_XORIGIN]).setScale(12, BigDecimal.ROUND_HALF_UP).add(XStart); 227 | TextView hoffsetView = (TextView) findViewById(R.id.timeoffset); 228 | 229 | if (hoffset.abs().compareTo(NANO) < 0) { 230 | String s = String.format(getString(R.string.timeoffset), hoffset.scaleByPowerOfTen(12), getString(R.string.picoseconds)); 231 | hoffsetView.setText(s); 232 | } else if (hoffset.abs().compareTo(MICRO) < 0) { 233 | String s = String.format(getString(R.string.timeoffset), hoffset.scaleByPowerOfTen(9), getString(R.string.nanoseconds)); 234 | hoffsetView.setText(s); 235 | } else if (hoffset.abs().compareTo(MILLI) < 0) { 236 | String s = String.format(getString(R.string.timeoffset), hoffset.scaleByPowerOfTen(6), getString(R.string.microseconds)); 237 | hoffsetView.setText(s); 238 | } else if (hoffset.abs().compareTo(BigDecimal.ONE) < 0) { 239 | String s = String.format(getString(R.string.timeoffset), hoffset.scaleByPowerOfTen(3), getString(R.string.milliseconds)); 240 | hoffsetView.setText(s); 241 | } else { 242 | String s = String.format(getString(R.string.timeoffset), hoffset, getString(R.string.seconds)); 243 | hoffsetView.setText(s); 244 | } 245 | } catch (ArrayIndexOutOfBoundsException e) { 246 | Log.w(TAG, "Incomplete data packet. Has the USB cable been unplugged?"); 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /app/src/main/java/com/geospark/scoperoid/Scope.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 GeoSpark 2 | // 3 | // Released under the MIT License (MIT) 4 | // See the LICENSE file, or visit http://opensource.org/licenses/MIT 5 | 6 | package com.geospark.scoperoid; 7 | 8 | import android.hardware.usb.UsbDevice; 9 | import android.hardware.usb.UsbDeviceConnection; 10 | import android.hardware.usb.UsbEndpoint; 11 | import android.hardware.usb.UsbInterface; 12 | import android.hardware.usb.UsbManager; 13 | import android.os.AsyncTask; 14 | import android.util.Log; 15 | 16 | import java.nio.ByteBuffer; 17 | import java.nio.ByteOrder; 18 | import java.util.ArrayDeque; 19 | import java.util.Arrays; 20 | import java.util.Queue; 21 | 22 | public class Scope { 23 | public static final String TAG = "USBTMC"; 24 | 25 | public static final String IEEE4882_CLS = "*CLS"; 26 | public static final String IEEE4882_ESE = "*ESE"; 27 | public static final String IEEE4882_ESE_Q = "*ESE?"; 28 | public static final String IEEE4882_ESR_Q = "*ESR?"; 29 | public static final String IEEE4882_IDN_Q = "*IDN?"; 30 | public static final String IEEE4882_OPC = "*OPC"; 31 | public static final String IEEE4882_OPC_Q = "*OPC?"; 32 | public static final String IEEE4882_RST = "*RST"; 33 | public static final String IEEE4882_SRE = "*SRE"; 34 | public static final String IEEE4882_SRE_Q = "*SRE?"; 35 | public static final String IEEE4882_STB_Q = "*STB?"; 36 | public static final String IEEE4882_TST_Q = "*TST?"; 37 | public static final String IEEE4882_WAI = "*WAI"; 38 | 39 | public static final String RUN = ":RUN"; 40 | public static final String STOP = ":STOP"; 41 | 42 | public static final String WAV_SOURCE = ":WAV:SOUR"; 43 | public static final String WAV_MODE = ":WAV:MODE"; 44 | public static final String WAV_FORMAT = ":WAV:FORM"; 45 | public static final String WAV_DATA_Q = ":WAV:DATA?"; 46 | public static final String WAV_XINCREMENT_Q = ":WAV:XINC?"; 47 | public static final String WAV_XORIGIN_Q = ":WAV:XOR?"; 48 | public static final String WAV_XREFERENCE_Q = ":WAV:XREF?"; 49 | public static final String WAV_YINCREMENT_Q = ":WAV:YINC?"; 50 | public static final String WAV_YORIGIN_Q = ":WAV:YOR?"; 51 | public static final String WAV_YREFERENCE_Q = ":WAV:YREF?"; 52 | public static final String WAV_START = ":WAV:STAR"; 53 | public static final String WAV_STOP = ":WAV:STOP"; 54 | public static final String WAV_PREAMBLE_Q = ":WAV:PRE?"; 55 | 56 | public static final String DISP_GBR = ":DISP:GBR"; 57 | 58 | public static final int WAV_PREAMBLE_FORMAT = 0; 59 | public static final int WAV_PREAMBLE_TYPE = 1; 60 | public static final int WAV_PREAMBLE_POINTS = 2; 61 | public static final int WAV_PREAMBLE_COUNT = 3; 62 | public static final int WAV_PREAMBLE_XINCREMENT = 4; 63 | public static final int WAV_PREAMBLE_XORIGIN = 5; 64 | public static final int WAV_PREAMBLE_XREFERENCE = 6; 65 | public static final int WAV_PREAMBLE_YINCREMENT = 7; 66 | public static final int WAV_PREAMBLE_YORIGIN = 8; 67 | public static final int WAV_PREAMBLE_YREFERENCE = 9; 68 | 69 | private static final byte USBTMC_MSGID_DEV_DEP_MSG_OUT = 1; 70 | private static final byte USBTMC_MSGID_DEV_DEP_MSG_IN = 2; 71 | 72 | public interface ScopeCallback { 73 | void result(String command, byte[] data); 74 | } 75 | 76 | private UsbDeviceConnection _connection = null; 77 | private UsbEndpoint _endpoint_in = null; 78 | private UsbEndpoint _endpoint_out = null; 79 | private int _max_packet_size = 64; 80 | private byte _mbtag = 0; 81 | private ByteBuffer _packet_buffer; 82 | private ByteBuffer _result_buffer; 83 | 84 | private Queue _command_queue = new ArrayDeque<>(); 85 | 86 | ScopeCallback _result_callback = null; 87 | 88 | public void register(ScopeCallback cb) { 89 | _result_callback = cb; 90 | } 91 | 92 | public void unregister() { 93 | _result_callback = null; 94 | } 95 | 96 | public Scope() { 97 | } 98 | 99 | public void connectUSB(UsbManager mgr, UsbDevice device) { 100 | UsbInterface device_interface = device.getInterface(0); 101 | _connection = mgr.openDevice(device); 102 | 103 | if (_connection != null) { 104 | _connection.claimInterface(device_interface, true); 105 | _endpoint_in = device_interface.getEndpoint(1); 106 | _endpoint_out = device_interface.getEndpoint(2); 107 | _max_packet_size = _endpoint_in.getMaxPacketSize(); 108 | _packet_buffer = ByteBuffer.allocate(_max_packet_size); 109 | _packet_buffer.order(ByteOrder.LITTLE_ENDIAN); 110 | _result_buffer = ByteBuffer.allocate(4096); 111 | } 112 | } 113 | 114 | public void disconnectUSB() { 115 | if (_connection != null) { 116 | _connection.close(); 117 | _connection = null; 118 | } 119 | } 120 | 121 | void postCommand(String command, String... params) { 122 | StringBuilder sb = new StringBuilder(); 123 | sb.append(command); 124 | 125 | for (String param : params) { 126 | sb.append(' '); 127 | sb.append(param); 128 | } 129 | 130 | _command_queue.add(sb.toString()); 131 | // Log.d("USBTMC >>>", command); 132 | 133 | if (_command_queue.size() == 1) { 134 | new GetDataTask().execute(); 135 | } 136 | } 137 | 138 | private class GetDataTask extends AsyncTask { 139 | private static final int USB_TIMEOUT = 1000; 140 | 141 | // Assumes for now command <= 52 bytes long. 142 | protected void send_command(String command) { 143 | _mbtag = (byte) ((_mbtag % 255) + 1); 144 | _packet_buffer.clear(); 145 | _packet_buffer.put(USBTMC_MSGID_DEV_DEP_MSG_OUT); 146 | _packet_buffer.put(_mbtag); 147 | _packet_buffer.put((byte) (_mbtag ^ 0xff)); 148 | _packet_buffer.put((byte) 0x00); 149 | 150 | _packet_buffer.putInt(command.length()); 151 | _packet_buffer.put((byte) 0x01); // EoM 152 | _packet_buffer.put((byte) 0x00); 153 | _packet_buffer.put((byte) 0x00); 154 | _packet_buffer.put((byte) 0x00); 155 | 156 | _packet_buffer.put(command.getBytes()); 157 | int length_padded = (_packet_buffer.position() + 3) - ((_packet_buffer.position() - 1) % 4); 158 | 159 | if (_connection != null) { 160 | _connection.bulkTransfer(_endpoint_out, _packet_buffer.array(), length_padded, USB_TIMEOUT); 161 | } 162 | } 163 | 164 | protected void request_data(int size) { 165 | _mbtag = (byte) ((_mbtag % 255) + 1); 166 | _packet_buffer.clear(); 167 | _packet_buffer.put(USBTMC_MSGID_DEV_DEP_MSG_IN); 168 | _packet_buffer.put(_mbtag); 169 | _packet_buffer.put((byte) (_mbtag ^ 0xff)); 170 | _packet_buffer.put((byte) 0x00); 171 | 172 | _packet_buffer.putInt(size); 173 | _packet_buffer.put((byte) 0x01); 174 | _packet_buffer.put((byte) 0x00); 175 | _packet_buffer.put((byte) 0x00); 176 | _packet_buffer.put((byte) 0x00); 177 | 178 | if (_connection != null) { 179 | _connection.bulkTransfer(_endpoint_out, _packet_buffer.array(), 12, USB_TIMEOUT); 180 | } 181 | } 182 | 183 | protected boolean receive_buffer(ByteBuffer bytes) { 184 | request_data(_max_packet_size - 12); 185 | 186 | _packet_buffer.clear(); 187 | 188 | if (_connection == null) { 189 | return false; 190 | } 191 | 192 | int ret_size = _connection.bulkTransfer(_endpoint_in, _packet_buffer.array(), _max_packet_size, USB_TIMEOUT); 193 | 194 | if (ret_size > 0) { 195 | _packet_buffer.position(ret_size); 196 | _packet_buffer.flip(); 197 | byte msgid = _packet_buffer.get(); 198 | byte btag = _packet_buffer.get(); 199 | byte btaginv = _packet_buffer.get(); 200 | byte unused = _packet_buffer.get(); 201 | 202 | int xfer_size = _packet_buffer.getInt(); 203 | byte xfer_attr = _packet_buffer.get(); 204 | unused = _packet_buffer.get(); 205 | unused = _packet_buffer.get(); 206 | unused = _packet_buffer.get(); 207 | boolean eom = (xfer_attr & 0x01) == 1; 208 | 209 | bytes.put(_packet_buffer); 210 | return !eom; 211 | } else { 212 | return false; 213 | } 214 | } 215 | 216 | @Override 217 | protected byte[] doInBackground(Void... params) { 218 | String command = _command_queue.peek(); 219 | send_command(command); 220 | 221 | if (command.endsWith("?")) { 222 | _result_buffer.clear(); 223 | while (receive_buffer(_result_buffer)) {} 224 | 225 | return Arrays.copyOf(_result_buffer.array(), _result_buffer.position()); 226 | } 227 | 228 | return null; 229 | } 230 | 231 | @Override 232 | protected void onPostExecute(byte[] result) { 233 | String command = _command_queue.remove(); 234 | // Log.d("USBTMC <<<", command); 235 | 236 | if (_command_queue.size() > 0) { 237 | new GetDataTask().execute(); 238 | } 239 | 240 | if (_result_callback != null) { 241 | _result_callback.result(command, result); 242 | } 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /app/src/main/java/com/geospark/scoperoid/ViewAspectRatioMeasurer.java: -------------------------------------------------------------------------------- 1 | package com.geospark.scoperoid; 2 | 3 | import android.view.View.MeasureSpec; 4 | 5 | /** 6 | * This class is a helper to measure views that require a specific aspect ratio.
7 | *
8 | * The measurement calculation is differing depending on whether the height and width 9 | * are fixed (match_parent or a dimension) or not (wrap_content) 10 | * 11 | *
 12 |  *                | Width fixed | Width dynamic |
 13 |  * ---------------+-------------+---------------|
 14 |  * Height fixed   |      1      |       2       |
 15 |  * ---------------+-------------+---------------|
 16 |  * Height dynamic |      3      |       4       |
 17 |  * 
18 | * Everything is measured according to a specific aspect ratio.
19 | *
20 | *
    21 | *
  • 1: Both width and height fixed: Fixed (Aspect ratio isn't respected)
  • 22 | *
  • 2: Width dynamic, height fixed: Set width depending on height
  • 23 | *
  • 3: Width fixed, height dynamic: Set height depending on width
  • 24 | *
  • 4: Both width and height dynamic: Largest size possible
  • 25 | *
26 | * 27 | * @author Jesper Borgstrup 28 | */ 29 | public class ViewAspectRatioMeasurer { 30 | 31 | private double aspectRatio; 32 | 33 | /** 34 | * Create a ViewAspectRatioMeasurer instance.
35 | *
36 | * Note: Don't construct a new instance everytime your View.onMeasure() method 37 | * is called.
38 | * Instead, create one instance when your View is constructed, and 39 | * use this instance's measure() methods in the onMeasure() method. 40 | * @param aspectRatio 41 | */ 42 | public ViewAspectRatioMeasurer(double aspectRatio) { 43 | this.aspectRatio = aspectRatio; 44 | } 45 | 46 | /** 47 | * Measure with the aspect ratio given at construction.
48 | *
49 | * After measuring, get the width and height with the {@link #getMeasuredWidth()} 50 | * and {@link #getMeasuredHeight()} methods, respectively. 51 | * @param widthMeasureSpec The width MeasureSpec passed in your View.onMeasure() method 52 | * @param heightMeasureSpec The height MeasureSpec passed in your View.onMeasure() method 53 | */ 54 | public void measure(int widthMeasureSpec, int heightMeasureSpec) { 55 | measure(widthMeasureSpec, heightMeasureSpec, this.aspectRatio); 56 | } 57 | 58 | /** 59 | * Measure with a specific aspect ratio
60 | *
61 | * After measuring, get the width and height with the {@link #getMeasuredWidth()} 62 | * and {@link #getMeasuredHeight()} methods, respectively. 63 | * @param widthMeasureSpec The width MeasureSpec passed in your View.onMeasure() method 64 | * @param heightMeasureSpec The height MeasureSpec passed in your View.onMeasure() method 65 | * @param aspectRatio The aspect ratio to calculate measurements in respect to 66 | */ 67 | public void measure(int widthMeasureSpec, int heightMeasureSpec, double aspectRatio) { 68 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 69 | int widthSize = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize(widthMeasureSpec); 70 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 71 | int heightSize = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( heightMeasureSpec ); 72 | 73 | if ( heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.EXACTLY ) { 74 | /* 75 | * Possibility 1: Both width and height fixed 76 | */ 77 | measuredWidth = widthSize; 78 | measuredHeight = heightSize; 79 | 80 | } else if ( heightMode == MeasureSpec.EXACTLY ) { 81 | /* 82 | * Possibility 2: Width dynamic, height fixed 83 | */ 84 | measuredWidth = (int) Math.min( widthSize, heightSize * aspectRatio ); 85 | measuredHeight = (int) (measuredWidth / aspectRatio); 86 | 87 | } else if ( widthMode == MeasureSpec.EXACTLY ) { 88 | /* 89 | * Possibility 3: Width fixed, height dynamic 90 | */ 91 | measuredHeight = (int) Math.min( heightSize, widthSize / aspectRatio ); 92 | measuredWidth = (int) (measuredHeight * aspectRatio); 93 | 94 | } else { 95 | /* 96 | * Possibility 4: Both width and height dynamic 97 | */ 98 | if ( widthSize > heightSize * aspectRatio ) { 99 | measuredHeight = heightSize; 100 | measuredWidth = (int)( measuredHeight * aspectRatio ); 101 | } else { 102 | measuredWidth = widthSize; 103 | measuredHeight = (int) (measuredWidth / aspectRatio); 104 | } 105 | 106 | } 107 | } 108 | 109 | private Integer measuredWidth = null; 110 | /** 111 | * Get the width measured in the latest call to measure(). 112 | */ 113 | public int getMeasuredWidth() { 114 | if ( measuredWidth == null ) { 115 | throw new IllegalStateException( "You need to run measure() before trying to get measured dimensions" ); 116 | } 117 | return measuredWidth; 118 | } 119 | 120 | private Integer measuredHeight = null; 121 | /** 122 | * Get the height measured in the latest call to measure(). 123 | */ 124 | public int getMeasuredHeight() { 125 | if ( measuredHeight == null ) { 126 | throw new IllegalStateException( "You need to run measure() before trying to get measured dimensions" ); 127 | } 128 | return measuredHeight; 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /app/src/main/java/com/geospark/scoperoid/WaveformGrid.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 GeoSpark 2 | // 3 | // Released under the MIT License (MIT) 4 | // See the LICENSE file, or visit http://opensource.org/licenses/MIT 5 | 6 | package com.geospark.scoperoid; 7 | 8 | import android.content.Context; 9 | import android.opengl.GLES20; 10 | 11 | import java.nio.ByteBuffer; 12 | import java.nio.ByteOrder; 13 | import java.nio.FloatBuffer; 14 | 15 | public class WaveformGrid { 16 | private static final String vertexShaderCode = 17 | "uniform mat4 uMVPMatrix;" + 18 | "attribute vec2 vPosition;" + 19 | "attribute vec2 aTexcoord;" + 20 | "varying vec2 vTexcoord;" + 21 | "void main() {" + 22 | " gl_Position = uMVPMatrix * vec4(vPosition, 0.3, 1.0);" + 23 | " vTexcoord = aTexcoord;" + 24 | "}"; 25 | 26 | private static final String fragmentShaderCode = 27 | "precision mediump float;" + 28 | "uniform float brightness;" + 29 | "uniform sampler2D texture;" + 30 | "varying vec2 vTexcoord;" + 31 | "void main() {" + 32 | " vec4 gridColour = texture2D(texture, vTexcoord);" + 33 | " gridColour.a *= brightness;" + 34 | " gl_FragColor = gridColour;" + 35 | "}"; 36 | private final int COORDS_PER_VERTEX = 2; 37 | private final int program; 38 | private final int vertexCount = 4; 39 | private final int vertexStride = COORDS_PER_VERTEX * 4; 40 | private float[] brightness = {0.5f}; 41 | private FloatBuffer vertexBuffer; 42 | private FloatBuffer coordBuffer; 43 | private int grid_tex; 44 | 45 | public WaveformGrid(Context context) { 46 | grid_tex = WaveformRenderer.loadTexture(context, R.drawable.grid); 47 | program = WaveformRenderer.loadShader(vertexShaderCode, fragmentShaderCode); 48 | 49 | ByteBuffer bb = ByteBuffer.allocateDirect(vertexCount * vertexStride); 50 | bb.order(ByteOrder.nativeOrder()); 51 | vertexBuffer = bb.asFloatBuffer(); 52 | vertexBuffer.clear(); 53 | vertexBuffer.put(0.0f); 54 | vertexBuffer.put(255.0f); 55 | vertexBuffer.put(0.0f); 56 | vertexBuffer.put(0.0f); 57 | vertexBuffer.put(1200.0f); 58 | vertexBuffer.put(255.0f); 59 | vertexBuffer.put(1200.0f); 60 | vertexBuffer.put(0.0f); 61 | vertexBuffer.flip(); 62 | 63 | ByteBuffer bbtex = ByteBuffer.allocateDirect(vertexCount * vertexStride); 64 | bbtex.order(ByteOrder.nativeOrder()); 65 | coordBuffer = bbtex.asFloatBuffer(); 66 | coordBuffer.clear(); 67 | coordBuffer.put(0.0f); 68 | coordBuffer.put(1.0f); 69 | coordBuffer.put(0.0f); 70 | coordBuffer.put(0.0f); 71 | coordBuffer.put(1.0f); 72 | coordBuffer.put(1.0f); 73 | coordBuffer.put(1.0f); 74 | coordBuffer.put(0.0f); 75 | coordBuffer.flip(); 76 | } 77 | 78 | public void setBrightness(float brightness) { 79 | this.brightness[0] = brightness; 80 | } 81 | 82 | public void draw(float[] matrix) { 83 | GLES20.glUseProgram(program); 84 | 85 | int MVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix"); 86 | GLES20.glUniformMatrix4fv(MVPMatrixHandle, 1, false, matrix, 0); 87 | 88 | int positionHandle = GLES20.glGetAttribLocation(program, "vPosition"); 89 | GLES20.glEnableVertexAttribArray(positionHandle); 90 | GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, 91 | GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); 92 | 93 | int coordHandle = GLES20.glGetAttribLocation(program, "aTexcoord"); 94 | GLES20.glEnableVertexAttribArray(coordHandle); 95 | GLES20.glVertexAttribPointer(coordHandle, COORDS_PER_VERTEX, 96 | GLES20.GL_FLOAT, false, vertexStride, coordBuffer); 97 | 98 | int brightnessHandle = GLES20.glGetUniformLocation(program, "brightness"); 99 | GLES20.glUniform1fv(brightnessHandle, 1, brightness, 0); 100 | 101 | int textureHandle = GLES20.glGetUniformLocation(program, "texture"); 102 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 103 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, grid_tex); 104 | GLES20.glUniform1i(textureHandle, 0); 105 | 106 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount); 107 | 108 | GLES20.glDisableVertexAttribArray(positionHandle); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/geospark/scoperoid/WaveformLine.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 GeoSpark 2 | // 3 | // Released under the MIT License (MIT) 4 | // See the LICENSE file, or visit http://opensource.org/licenses/MIT 5 | 6 | package com.geospark.scoperoid; 7 | 8 | import android.opengl.GLES20; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.nio.ByteOrder; 12 | import java.nio.FloatBuffer; 13 | 14 | public class WaveformLine { 15 | private static final String vertexShaderCode = 16 | "uniform mat4 uMVPMatrix;" + 17 | "attribute vec2 vPosition;" + 18 | "void main() {" + 19 | " gl_Position = uMVPMatrix * vec4(vPosition, 0.2, 1.0);" + 20 | "}"; 21 | 22 | private static final String fragmentShaderCode = 23 | "precision mediump float;" + 24 | "uniform vec4 vColor;" + 25 | "void main() {" + 26 | " gl_FragColor = vColor;" + 27 | "}"; 28 | 29 | private final int COORDS_PER_VERTEX = 2; 30 | private final int program; 31 | private final int vertexCount = 1200; 32 | private final int vertexStride = COORDS_PER_VERTEX * 4; 33 | private float[] colour; 34 | 35 | private FloatBuffer vertexBuffer; 36 | 37 | public WaveformLine(float r, float g, float b) { 38 | colour = new float[4]; 39 | colour[0] = r; 40 | colour[1] = g; 41 | colour[2] = b; 42 | colour[3] = 1.0f; 43 | program = WaveformRenderer.loadShader(vertexShaderCode, fragmentShaderCode); 44 | ByteBuffer bb = ByteBuffer.allocateDirect(vertexCount * vertexStride); 45 | bb.order(ByteOrder.nativeOrder()); 46 | vertexBuffer = bb.asFloatBuffer(); 47 | } 48 | 49 | public void setData(byte[] data) { 50 | vertexBuffer.clear(); 51 | 52 | for (int i = 0; i < Math.min(vertexCount, data.length); ++i) { 53 | vertexBuffer.put((float)i); 54 | float y = (float)(data[i] & 0xff); 55 | // Empirically derived numbers to make the waveform fit the grid. 56 | vertexBuffer.put((y * 1.285f) - 35.0f); 57 | } 58 | 59 | vertexBuffer.flip(); 60 | } 61 | 62 | public void draw(float[] matrix) { 63 | GLES20.glUseProgram(program); 64 | 65 | int MVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix"); 66 | GLES20.glUniformMatrix4fv(MVPMatrixHandle, 1, false, matrix, 0); 67 | 68 | int positionHandle = GLES20.glGetAttribLocation(program, "vPosition"); 69 | GLES20.glEnableVertexAttribArray(positionHandle); 70 | GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, 71 | GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); 72 | 73 | int colorHandle = GLES20.glGetUniformLocation(program, "vColor"); 74 | GLES20.glUniform4fv(colorHandle, 1, colour, 0); 75 | 76 | GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 0, vertexCount); 77 | 78 | GLES20.glDisableVertexAttribArray(positionHandle); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/geospark/scoperoid/WaveformRenderer.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 GeoSpark 2 | // 3 | // Released under the MIT License (MIT) 4 | // See the LICENSE file, or visit http://opensource.org/licenses/MIT 5 | 6 | package com.geospark.scoperoid; 7 | 8 | import android.content.Context; 9 | import android.graphics.Bitmap; 10 | import android.graphics.BitmapFactory; 11 | import android.graphics.Color; 12 | import android.opengl.GLES20; 13 | import android.opengl.GLSurfaceView; 14 | import android.opengl.GLUtils; 15 | import android.opengl.Matrix; 16 | import android.os.Bundle; 17 | import android.os.Handler; 18 | import android.os.Message; 19 | 20 | import javax.microedition.khronos.egl.EGLConfig; 21 | import javax.microedition.khronos.opengles.GL10; 22 | 23 | public class WaveformRenderer implements GLSurfaceView.Renderer { 24 | private final float[] mMVPMatrix = new float[16]; 25 | private final float[] mProjectionMatrix = new float[16]; 26 | private final float[] mViewMatrix = new float[16]; 27 | private Context context; 28 | private byte[] waveform_data = null; 29 | private WaveformLine line; 30 | private WaveformGrid grid; 31 | 32 | public Handler handler; 33 | 34 | Handler.Callback callback = new Handler.Callback() { 35 | @Override 36 | public boolean handleMessage(Message msg) { 37 | Bundle b = msg.getData(); 38 | waveform_data = b.getByteArray("waveform"); 39 | return true; 40 | } 41 | }; 42 | 43 | public WaveformRenderer(Context context) { 44 | handler = new Handler(callback); 45 | this.context = context; 46 | } 47 | 48 | public static int loadShader(String vertexShaderCode, String fragmentShaderCode) { 49 | int vs = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER); 50 | int fs = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER); 51 | int program = GLES20.glCreateProgram(); 52 | 53 | GLES20.glShaderSource(vs, vertexShaderCode); 54 | GLES20.glShaderSource(fs, fragmentShaderCode); 55 | GLES20.glCompileShader(vs); 56 | GLES20.glCompileShader(fs); 57 | GLES20.glAttachShader(program, vs); 58 | GLES20.glAttachShader(program, fs); 59 | GLES20.glLinkProgram(program); 60 | 61 | return program; 62 | } 63 | 64 | public static int loadTexture(final Context context, final int resourceId) { 65 | final int[] textureHandle = new int[1]; 66 | 67 | GLES20.glGenTextures(1, textureHandle, 0); 68 | 69 | if (textureHandle[0] != 0) { 70 | final BitmapFactory.Options options = new BitmapFactory.Options(); 71 | options.inScaled = false; 72 | final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options); 73 | 74 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]); 75 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 76 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 77 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 78 | bitmap.recycle(); 79 | } else { 80 | throw new RuntimeException("Error loading texture."); 81 | } 82 | 83 | return textureHandle[0]; 84 | } 85 | 86 | @Override 87 | public void onSurfaceCreated(GL10 unused, EGLConfig config) { 88 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 89 | GLES20.glDisable(GLES20.GL_DEPTH_TEST); 90 | GLES20.glEnable(GLES20.GL_BLEND); 91 | GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); 92 | int c = context.getResources().getColor(R.color.channel1High); 93 | float r = (float)Color.red(c) / 255.0f; 94 | float g = (float)Color.green(c) / 255.0f; 95 | float b = (float)Color.blue(c) / 255.0f; 96 | line = new WaveformLine(r, g, b); 97 | grid = new WaveformGrid(context); 98 | grid.setBrightness(0.75f); 99 | } 100 | 101 | @Override 102 | public void onSurfaceChanged(GL10 unused, int width, int height) { 103 | GLES20.glViewport(0, 0, width, height); 104 | Matrix.orthoM(mProjectionMatrix, 0, 0.0f, 1200.0f, 0.0f, 255.0f, 0.1f, 10.0f); 105 | } 106 | 107 | @Override 108 | public void onDrawFrame(GL10 unused) { 109 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 110 | Matrix.setLookAtM(mViewMatrix, 0, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); 111 | Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0); 112 | 113 | grid.draw(mMVPMatrix); 114 | 115 | if (waveform_data != null) { 116 | line.setData(waveform_data); 117 | line.draw(mMVPMatrix); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/geospark/scoperoid/WaveformView.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 GeoSpark 2 | // 3 | // Released under the MIT License (MIT) 4 | // See the LICENSE file, or visit http://opensource.org/licenses/MIT 5 | 6 | package com.geospark.scoperoid; 7 | 8 | import android.content.Context; 9 | import android.content.res.TypedArray; 10 | import android.opengl.GLSurfaceView; 11 | import android.os.Bundle; 12 | import android.os.Message; 13 | import android.util.AttributeSet; 14 | import android.util.Log; 15 | 16 | import java.io.UnsupportedEncodingException; 17 | import java.util.Arrays; 18 | 19 | 20 | public class WaveformView extends GLSurfaceView { 21 | private int mAspectRatioWidth; 22 | private int mAspectRatioHeight; 23 | private final WaveformRenderer mRenderer; 24 | private static final double VIEW_ASPECT_RATIO = 1.5; 25 | private ViewAspectRatioMeasurer varm = new ViewAspectRatioMeasurer(VIEW_ASPECT_RATIO); 26 | 27 | public WaveformView(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | 30 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveformView); 31 | 32 | mAspectRatioWidth = a.getInt(R.styleable.WaveformView_aspectRatioWidth, 1); 33 | mAspectRatioHeight = a.getInt(R.styleable.WaveformView_aspectRatioHeight, 1); 34 | 35 | a.recycle(); 36 | 37 | setEGLContextClientVersion(2); 38 | mRenderer = new WaveformRenderer(context); 39 | 40 | setRenderer(mRenderer); 41 | } 42 | 43 | public void setWaveformData(byte[] data) { 44 | // Bit of an assumption, ideally we read in the first two bytes to determine the length 45 | // of the rest of the header, but meh. 46 | byte[] hdr = Arrays.copyOfRange(data, 2, 11); 47 | int data_len; 48 | 49 | try { 50 | String s = new String(hdr, "US-ASCII"); 51 | data_len = Integer.parseInt(s); 52 | } catch (UnsupportedEncodingException e) { 53 | return; 54 | } 55 | 56 | Message msg = Message.obtain(mRenderer.handler); 57 | Bundle b = new Bundle(); 58 | b.putByteArray("waveform", Arrays.copyOfRange(data, 11, Math.min(data.length, data_len + 12))); 59 | msg.setData(b); 60 | msg.sendToTarget(); 61 | } 62 | 63 | @Override 64 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 65 | varm.measure(widthMeasureSpec, heightMeasureSpec); 66 | setMeasuredDimension(varm.getMeasuredWidth(), varm.getMeasuredHeight()); 67 | // int originalWidth = MeasureSpec.getSize(widthMeasureSpec); 68 | // int originalHeight = MeasureSpec.getSize(heightMeasureSpec); 69 | // int calculatedHeight = originalWidth * mAspectRatioHeight / mAspectRatioWidth; 70 | // 71 | // int finalWidth, finalHeight; 72 | // 73 | // if (calculatedHeight > originalHeight) { 74 | // finalWidth = originalHeight * mAspectRatioWidth / mAspectRatioHeight; 75 | // finalHeight = originalHeight; 76 | // } else { 77 | // finalWidth = originalWidth; 78 | // finalHeight = calculatedHeight; 79 | // } 80 | // 81 | //// float ar = (float)finalWidth / (float)finalHeight; 82 | //// Log.d("GRID", String.valueOf(finalWidth) + " x " + String.valueOf(finalHeight) + " (" + String.valueOf(ar) + ")"); 83 | // 84 | // super.onMeasure( 85 | // MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), 86 | // MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeoSpark/scoperoid/65c804cc42e7905fa661c93210da7ad9016d0c2d/app/src/main/res/drawable/button.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeoSpark/scoperoid/65c804cc42e7905fa661c93210da7ad9016d0c2d/app/src/main/res/drawable/grid.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/rigol_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeoSpark/scoperoid/65c804cc42e7905fa661c93210da7ad9016d0c2d/app/src/main/res/drawable/rigol_logo.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 17 | 18 | 26 | 27 |